一,为什么oauth2要整合jwt?

1,OAuth2的token技术有一个最大的问题是不携带用户信息,所以资源服务器不能进行本地验证,

以致每次对于资源的访问,资源服务器都需要向认证服务器的token存储发起请求,

一是验证token的有效性,二是获取token对应的用户信息。

有大量的请求时会导致处理效率降低,

而且认证服务器作为一个中心节点,

对于SLA和处理性能等均有很高的要求

对于分布式架构都是可能引发问题的隐患

2,jwt技术的两个优势:

token的签名验证可以直接在资源服务器本地完成,不需要再次连接认证服务器

jwt的payload部分可以保存用户相关信息,这样直接有了token和用户信息的绑定

3,spring security oauth2生成token时的输出默认格式不能直接修改,

演示例子中通过增加一个controller实现了输出的格式化

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息

1,项目地址:

  1. https://github.com/liuhongdi/securityoauth2jwt

2,项目功能说明:

演示了使用jwt存储oauth2的token

3,项目结构:如图:

三,配置文件说明

1,pom.xml

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <!--security begin-->
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-security</artifactId>
  9. </dependency>
  10. <!--oauth2 begin-->
  11. <dependency>
  12. <groupId>org.springframework.security.oauth</groupId>
  13. <artifactId>spring-security-oauth2</artifactId>
  14. <version>2.5.0.RELEASE</version>
  15. </dependency>
  16. <!--jwt begin-->
  17. <dependency>
  18. <groupId>org.springframework.security</groupId>
  19. <artifactId>spring-security-jwt</artifactId>
  20. <version>1.1.1.RELEASE</version>
  21. </dependency>
  22. <!--mysql mybatis begin-->
  23. <dependency>
  24. <groupId>org.mybatis.spring.boot</groupId>
  25. <artifactId>mybatis-spring-boot-starter</artifactId>
  26. <version>2.1.3</version>
  27. </dependency>
  28. <dependency>
  29. <groupId>mysql</groupId>
  30. <artifactId>mysql-connector-java</artifactId>
  31. <scope>runtime</scope>
  32. </dependency>
  33. <!--fastjson begin-->
  34. <dependency>
  35. <groupId>com.alibaba</groupId>
  36. <artifactId>fastjson</artifactId>
  37. <version>1.2.73</version>
  38. </dependency>
  39. <!--jaxb-->
  40. <dependency>
  41. <groupId>javax.xml.bind</groupId>
  42. <artifactId>jaxb-api</artifactId>
  43. <version>2.3.0</version>
  44. </dependency>
  45. <dependency>
  46. <groupId>com.sun.xml.bind</groupId>
  47. <artifactId>jaxb-impl</artifactId>
  48. <version>2.3.0</version>
  49. </dependency>
  50. <dependency>
  51. <groupId>com.sun.xml.bind</groupId>
  52. <artifactId>jaxb-core</artifactId>
  53. <version>2.3.0</version>
  54. </dependency>
  55. <dependency>
  56. <groupId>javax.activation</groupId>
  57. <artifactId>activation</artifactId>
  58. <version>1.1.1</version>
  59. </dependency>
  60. <!--jjwt begin-->
  61. <dependency>
  62. <groupId>io.jsonwebtoken</groupId>
  63. <artifactId>jjwt</artifactId>
  64. <version>0.9.1</version>
  65. </dependency>
  66. <!--validation begin-->
  67. <dependency>
  68. <groupId>org.springframework.boot</groupId>
  69. <artifactId>spring-boot-starter-validation</artifactId>
  70. </dependency>

2,application.properties

  1. #mysql
  2. spring.datasource.url=jdbc:mysql://localhost:3306/security?characterEncoding=utf8&useSSL=false
  3. spring.datasource.username=root
  4. spring.datasource.password=lhddemo
  5. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
  6.  
  7. #mybatis
  8. mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
  9. mybatis.type-aliases-package=com.example.demo.mapper
  10.  
  11. #error
  12. server.error.include-stacktrace=always
  13. #log
  14. logging.level.org.springframework.web=trace

3,数据库:

建表sql:

  1. CREATE TABLE `sys_user` (
  2. `userId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  3. `userName` varchar(100) NOT NULL DEFAULT '' COMMENT '用户名',
  4. `password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
  5. `nickName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '昵称',
  6. PRIMARY KEY (`userId`),
  7. UNIQUE KEY `userName` (`userName`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'
  1. INSERT INTO `sys_user` (`userId`, `userName`, `password`, `nickName`) VALUES
  2. (1, 'lhd', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '老刘'),
  3. (2, 'admin', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '管理员'),
  4. (3, 'merchant', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '商户老张');

说明:3个密码都是111111,仅供演示使用

  1. CREATE TABLE `sys_user_role` (
  2. `urId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  3. `userId` int(11) NOT NULL DEFAULT '0' COMMENT '用户id',
  4. `roleName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '角色id',
  5. PRIMARY KEY (`urId`),
  6. UNIQUE KEY `userId` (`userId`,`roleName`)
  7. ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户角色关联表'
  1. INSERT INTO `sys_user_role` (`urId`, `userId`, `roleName`) VALUES
  2. (1, 2, 'ADMIN'),
  3. (2, 3, 'MERCHANT');

四,java代码说明

1,WebSecurityConfig.java

  1. @Configuration
  2. @EnableWebSecurity
  3. @EnableGlobalMethodSecurity(prePostEnabled = true)
  4. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  5. private final static BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder();
  6. @Resource
  7. private SecUserDetailService secUserDetailService; //用户信息类,用来得到UserDetails
  8.  
  9. @Bean
  10. @Override
  11. protected AuthenticationManager authenticationManager() throws Exception {
  12. return super.authenticationManager();
  13. }
  14. @Bean
  15. @Override
  16. protected UserDetailsService userDetailsService() {
  17. return super.userDetailsService();
  18. }
  19.  
  20. @Bean
  21. PasswordEncoder passwordEncoder() {
  22. return new BCryptPasswordEncoder();
  23. }
  24.  
  25. @Override
  26. protected void configure(HttpSecurity http) throws Exception {
  27. http.csrf().disable();
  28. http.antMatcher("/oauth/**")
  29. .authorizeRequests()
  30. .antMatchers("/oauth/**").permitAll()
  31. .and().csrf().disable();
  32. }
  33.  
  34. @Resource
  35. public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  36. auth.userDetailsService(secUserDetailService).passwordEncoder(new PasswordEncoder() {
  37. @Override
  38. public String encode(CharSequence charSequence) {
  39. return ENCODER.encode(charSequence);
  40. }
  41. //密码匹配,看输入的密码经过加密与数据库中存放的是否一样
  42. @Override
  43. public boolean matches(CharSequence charSequence, String s) {
  44. return ENCODER.matches(charSequence,s);
  45. }
  46. });
  47. }
  48. }

2,AuthorizationServerConfig.java

  1. @Configuration
  2. @EnableAuthorizationServer
  3. public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  4. @Resource
  5. AuthenticationManager authenticationManager;
  6.  
  7. @Resource
  8. UserDetailsService userDetailsService;
  9.  
  10. @Resource
  11. TokenStore jwtTokenStore;
  12.  
  13. @Resource
  14. JwtAccessTokenConverter jwtAccessTokenConverter;
  15.  
  16. @Override
  17. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  18. String clientId = "client_id";
  19. String clientSecret = "123";
  20. clients.inMemory()
  21. //这个好比账号
  22. .withClient(clientId)
  23. //授权同意的类型
  24. .authorizedGrantTypes("password", "refresh_token")
  25. //有效时间
  26. .accessTokenValiditySeconds(1800)
  27. .refreshTokenValiditySeconds(60 * 60 * 2)
  28. .resourceIds("rid")
  29. //作用域,范围
  30. .scopes("all")
  31. //密码
  32. .secret(new BCryptPasswordEncoder().encode(clientSecret));
  33. }
  34.  
  35. @Override
  36. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  37. endpoints.tokenStore(jwtTokenStore)
  38. .authenticationManager(authenticationManager)
  39. .userDetailsService(userDetailsService)
  40. .accessTokenConverter(jwtAccessTokenConverter);
  41. }
  42.  
  43. @Override
  44. public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
  45. //允许客户端表单身份验证
  46. security.allowFormAuthenticationForClients();
  47. }
  48. }

授权服务器的配置

3,ResourceServerConfig.java

  1. @Configuration
  2. @EnableResourceServer
  3. public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
  4. private static final String RESOURCE_ID = "rid";
  5.  
  6. @Resource
  7. private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
  8.  
  9. @Resource
  10. private RestAccessDeniedHandler restAccessDeniedHandler;
  11.  
  12. @Override
  13. public void configure(ResourceServerSecurityConfigurer resources) {
  14. resources.resourceId(RESOURCE_ID).stateless(false)
  15. .authenticationEntryPoint(restAuthenticationEntryPoint);
  16. }
  17.  
  18. @Override
  19. public void configure(HttpSecurity http) throws Exception {
  20. http.authorizeRequests()
  21. .antMatchers("/admin/**").hasAnyRole("admin","ADMIN");
  22. http.
  23. anonymous().disable()
  24. .authorizeRequests()
  25. .antMatchers("/users/**").access("hasRole('ADMIN')")
  26. .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
  27.  
  28. //http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);
  29. http.exceptionHandling().accessDeniedHandler(restAccessDeniedHandler);
  30. }
  31. }

资源服务器的配置

4,RestAccessDeniedHandler.java

  1. @Component("restAccessDeniedHandler")
  2. public class RestAccessDeniedHandler implements AccessDeniedHandler {
  3. //处理权限不足的情况:403,对应:access_denied
  4. @Override
  5. public void handle(HttpServletRequest request, HttpServletResponse response,
  6. AccessDeniedException accessDeniedException)
  7. throws IOException, ServletException {
  8. System.out.println("-------RestAccessDeniedHandler");
  9. ServletUtil.printRestResult(RestResult.error(403,"权限不够访问当前资源,被拒绝"));
  10. }
  11. }

遇到access deny情况的处理

5,RestAuthenticationEntryPoint.java

  1. @Component("restAuthenticationEntryPoint")
  2. public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
  3. //返回未得到授权时的报错:对应:invalid_token
  4. @Override
  5. public void commence(
  6. HttpServletRequest request,
  7. HttpServletResponse response,
  8. AuthenticationException authException) throws IOException {
  9. //System.out.println("commence");
  10. ServletUtil.printRestResult(RestResult.error(401,"未得到授权"));
  11. }
  12. }

匿名用户无权访问时的处理

6,JwtTokenConfig.java

  1. @Configuration
  2. public class JwtTokenConfig {
  3. @Bean
  4. public TokenStore jwtTokenStore(){
  5. return new JwtTokenStore(jwtAccessTokenConverter());
  6. }
  7.  
  8. //使用Jwt来作为token的生成
  9. @Bean
  10. public JwtAccessTokenConverter jwtAccessTokenConverter(){
  11. JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
  12. accessTokenConverter.setSigningKey("internet_plus");
  13. return accessTokenConverter;
  14. }
  15.  
  16. @Bean
  17. public TokenEnhancer jwtTokenEnhancer(){
  18. return new JwtTokenEnhancer ();
  19. }
  20. }

配置jwttoken,指定了signkey

7,JwtTokenEnhancer.java

  1. public class JwtTokenEnhancer implements TokenEnhancer {
  2.  
  3. @Override
  4. public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
  5. Map<String,Object> info = new HashMap<>();
  6. info.put("provider","haolarn");
  7. //设置附加信息
  8. ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info);
  9. return null;
  10. }
  11. }

返回token信息中的附加信息

8,OauthController.java

  1. @RestController
  2. @RequestMapping("/oauth")
  3. public class OauthController {
  4.  
  5. @Autowired
  6. private TokenEndpoint tokenEndpoint;
  7.  
  8. //自定义返回信息添加基本信息
  9. @PostMapping("/token")
  10. public RestResult postAccessTokenWithUserInfo(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
  11. OAuth2AccessToken accessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
  12. Map<String, Object> data = new LinkedHashMap();
  13. data.put("accessToken", accessToken.getValue());
  14. data.put("token_type", accessToken.getTokenType());
  15. data.put("refreshToken", accessToken.getRefreshToken().getValue());
  16. data.put("scope", accessToken.getScope());
  17. data.put("expires_in", accessToken.getExpiresIn());
  18. data.put("jti", accessToken.getAdditionalInformation().get("jti"));
  19. return RestResult.success(data);
  20. }
  21.  
  22. }

格式化生成token时的返回json信息

9,其他非关键代码可以访问github查看,不再一一贴出

五,测试效果

1,得到基于jwt的token

访问:http://127.0.0.1:8080/oauth/token

返回结果:

2,用得到的access_token访问admin/hello:查看当前用户和role:

  1. http://127.0.0.1:8080/admin/hello

返回:

3,换一个无权限的账号登录:

访问admin/hello时会提示无权限

六,查看spring boot的版本:

  1. . ____ _ __ _ _
  2. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
  3. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  4. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
  5. ' |____| .__|_| |_|_| |_\__, | / / / /
  6. =========|_|==============|___/=/_/_/_/
  7. :: Spring Boot :: (v2.3.3.RELEASE)

spring boot:spring security实现oauth2+jwt管理认证授权及oauth2返回结果格式化(spring boot 2.3.3)的更多相关文章

  1. [认证授权] 2.OAuth2授权(续) & JWT(JSON Web Token)

    1 RFC6749还有哪些可以完善的? 1.1 撤销Token 在上篇[认证授权] 1.OAuth2授权中介绍到了OAuth2可以帮我们解决第三方Client访问受保护资源的问题,但是只提供了如何获得 ...

  2. [认证授权] 2.OAuth2(续) & JSON Web Token

    0. RFC6749还有哪些可以完善的? 0.1. 撤销Token 在上篇[认证授权] 1.OAuth2授权中介绍到了OAuth2可以帮我们解决第三方Client访问受保护资源的问题,但是只提供了如何 ...

  3. Spring Cloud实战 | 最终篇:Spring Cloud Gateway+Spring Security OAuth2集成统一认证授权平台下实现注销使JWT失效方案

    一. 前言 在上一篇文章介绍 youlai-mall 项目中,通过整合Spring Cloud Gateway.Spring Security OAuth2.JWT等技术实现了微服务下统一认证授权平台 ...

  4. asp.net core 3.1多种身份验证方案,cookie和jwt混合认证授权

    开发了一个公司内部系统,使用asp.net core 3.1.在开发用户认证授权使用的是简单的cookie认证方式,然后开发好了要写几个接口给其它系统调用数据.并且只是几个简单的接口不准备再重新部署一 ...

  5. Spring cloud微服务实战——基于OAUTH2.0统一认证授权的微服务基础架构

    https://blog.csdn.net/w1054993544/article/details/78932614

  6. [认证授权] 1.OAuth2授权

    1 OAuth2解决什么问题的? 举个栗子先.小明在QQ空间积攒了多年的照片,想挑选一些照片来打印出来.然后小明在找到一家提供在线打印并且包邮的网站(我们叫它PP吧(Print Photo缩写

  7. 【Spring Cloud & Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权

    一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证授权.鉴权的逻辑,结合 ...

  8. Spring Security OAuth2.0认证授权五:用户信息扩展到jwt

    历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...

  9. Spring Security OAuth2.0认证授权四:分布式系统认证授权

    Spring Security OAuth2.0认证授权系列文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授 ...

随机推荐

  1. Minimizing maximizer(POJ 1769)

    原题如下: Minimizing maximizer Time Limit: 5000MS   Memory Limit: 30000K Total Submissions: 5104   Accep ...

  2. Appium之定位元素

     常用的appium元素定位工具: (1)Android SDK 中提供的元素定位工具uiautomatorviewer: (2)AppiumDesktop提供的元素定位工具Appium Inspec ...

  3. pip更新命令

    python -m pip install --upgrade pip 更新时如果报错'NoneType' object has no attribute 'bytes', 解决办法:easy_ins ...

  4. swift基本数据类型使用-数组使用

    目录 数组的使用 1.数组的定义 2.对可变数组的基本操作 3.数组的遍历 4.数组的合并 5. 示例 数组的使用 1.数组的定义 1> 定义不可变数组 2> 定义可变数组 2.对可变数组 ...

  5. [IDEA]Java:“程序包XXX不存在”问题的三种解决方案

    ###三种方案 ####01 出现jar包找不到的问题,首先有可能是项目依赖中有些jar没有下载完整 而mvn idea:idea这个命令可以检查并继续下载未下载完整的依赖jar. 在命令行输入mvn ...

  6. charles常用功能 request和response(简单的操作)

    先介绍一个修改request请求参数值的方法吧 第一步: 拷贝完成后还需要配置一下: 先添加一个: 然后下一步: 最后点击OK,就可以开始操作request和response数据了 先修改reques ...

  7. 有向图的基本算法-Java实现

    有向图 有向图同无向图的区别为每条边带有方向,表明从一个顶点至另一个顶点可达.有向图的算法多依赖深度搜索算法. 本文主要介绍有向图的基本算法,涉及图的表示.可达性.检测环.图的遍历.拓扑排序以及强连通 ...

  8. websocket直接绕过JS加密的方式

    目录 websocket--hook 服务端--WebSocketServer.js 客户端注入JS代码 python开端口 get_data.py 文件方式 get_user_id.py 文件方式 ...

  9. 利用glog打印日志

    glog出自互联网豪门google,质量有保证,轻量级,入门简单,功能较全,线程安全.有关glog的打印细节本篇文章不再赘述,网上一大堆的资料,参考:glog日志库使用笔记. glog的托管地址:gi ...

  10. centos7 下 docker 安装

    前提: 目前,CentOS 仅发行版本中的内核支持 Docker. Docker 运行在 CentOS 7 上,要求系统为64位.系统内核版本为 3.10 以上. Docker 运行在 CentOS- ...