spring boot:spring security实现oauth2+jwt管理认证授权及oauth2返回结果格式化(spring boot 2.3.3)
一,为什么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,项目地址:
- https://github.com/liuhongdi/securityoauth2jwt
2,项目功能说明:
演示了使用jwt存储oauth2的token
3,项目结构:如图:
三,配置文件说明
1,pom.xml
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!--security begin-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
- <!--oauth2 begin-->
- <dependency>
- <groupId>org.springframework.security.oauth</groupId>
- <artifactId>spring-security-oauth2</artifactId>
- <version>2.5.0.RELEASE</version>
- </dependency>
- <!--jwt begin-->
- <dependency>
- <groupId>org.springframework.security</groupId>
- <artifactId>spring-security-jwt</artifactId>
- <version>1.1.1.RELEASE</version>
- </dependency>
- <!--mysql mybatis begin-->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>2.1.3</version>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
- <!--fastjson begin-->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.73</version>
- </dependency>
- <!--jaxb-->
- <dependency>
- <groupId>javax.xml.bind</groupId>
- <artifactId>jaxb-api</artifactId>
- <version>2.3.0</version>
- </dependency>
- <dependency>
- <groupId>com.sun.xml.bind</groupId>
- <artifactId>jaxb-impl</artifactId>
- <version>2.3.0</version>
- </dependency>
- <dependency>
- <groupId>com.sun.xml.bind</groupId>
- <artifactId>jaxb-core</artifactId>
- <version>2.3.0</version>
- </dependency>
- <dependency>
- <groupId>javax.activation</groupId>
- <artifactId>activation</artifactId>
- <version>1.1.1</version>
- </dependency>
- <!--jjwt begin-->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
- <!--validation begin-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-validation</artifactId>
- </dependency>
2,application.properties
- #mysql
- spring.datasource.url=jdbc:mysql://localhost:3306/security?characterEncoding=utf8&useSSL=false
- spring.datasource.username=root
- spring.datasource.password=lhddemo
- spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- #mybatis
- mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
- mybatis.type-aliases-package=com.example.demo.mapper
- #error
- server.error.include-stacktrace=always
- #log
- logging.level.org.springframework.web=trace
3,数据库:
建表sql:
- CREATE TABLE `sys_user` (
- `userId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
- `userName` varchar(100) NOT NULL DEFAULT '' COMMENT '用户名',
- `password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
- `nickName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '昵称',
- PRIMARY KEY (`userId`),
- UNIQUE KEY `userName` (`userName`)
- ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'
- INSERT INTO `sys_user` (`userId`, `userName`, `password`, `nickName`) VALUES
- (1, 'lhd', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '老刘'),
- (2, 'admin', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '管理员'),
- (3, 'merchant', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '商户老张');
说明:3个密码都是111111,仅供演示使用
- CREATE TABLE `sys_user_role` (
- `urId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
- `userId` int(11) NOT NULL DEFAULT '0' COMMENT '用户id',
- `roleName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '角色id',
- PRIMARY KEY (`urId`),
- UNIQUE KEY `userId` (`userId`,`roleName`)
- ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户角色关联表'
- INSERT INTO `sys_user_role` (`urId`, `userId`, `roleName`) VALUES
- (1, 2, 'ADMIN'),
- (2, 3, 'MERCHANT');
四,java代码说明
1,WebSecurityConfig.java
- @Configuration
- @EnableWebSecurity
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
- private final static BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder();
- @Resource
- private SecUserDetailService secUserDetailService; //用户信息类,用来得到UserDetails
- @Bean
- @Override
- protected AuthenticationManager authenticationManager() throws Exception {
- return super.authenticationManager();
- }
- @Bean
- @Override
- protected UserDetailsService userDetailsService() {
- return super.userDetailsService();
- }
- @Bean
- PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.csrf().disable();
- http.antMatcher("/oauth/**")
- .authorizeRequests()
- .antMatchers("/oauth/**").permitAll()
- .and().csrf().disable();
- }
- @Resource
- public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(secUserDetailService).passwordEncoder(new PasswordEncoder() {
- @Override
- public String encode(CharSequence charSequence) {
- return ENCODER.encode(charSequence);
- }
- //密码匹配,看输入的密码经过加密与数据库中存放的是否一样
- @Override
- public boolean matches(CharSequence charSequence, String s) {
- return ENCODER.matches(charSequence,s);
- }
- });
- }
- }
2,AuthorizationServerConfig.java
- @Configuration
- @EnableAuthorizationServer
- public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
- @Resource
- AuthenticationManager authenticationManager;
- @Resource
- UserDetailsService userDetailsService;
- @Resource
- TokenStore jwtTokenStore;
- @Resource
- JwtAccessTokenConverter jwtAccessTokenConverter;
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
- String clientId = "client_id";
- String clientSecret = "123";
- clients.inMemory()
- //这个好比账号
- .withClient(clientId)
- //授权同意的类型
- .authorizedGrantTypes("password", "refresh_token")
- //有效时间
- .accessTokenValiditySeconds(1800)
- .refreshTokenValiditySeconds(60 * 60 * 2)
- .resourceIds("rid")
- //作用域,范围
- .scopes("all")
- //密码
- .secret(new BCryptPasswordEncoder().encode(clientSecret));
- }
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- endpoints.tokenStore(jwtTokenStore)
- .authenticationManager(authenticationManager)
- .userDetailsService(userDetailsService)
- .accessTokenConverter(jwtAccessTokenConverter);
- }
- @Override
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- //允许客户端表单身份验证
- security.allowFormAuthenticationForClients();
- }
- }
授权服务器的配置
3,ResourceServerConfig.java
- @Configuration
- @EnableResourceServer
- public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
- private static final String RESOURCE_ID = "rid";
- @Resource
- private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
- @Resource
- private RestAccessDeniedHandler restAccessDeniedHandler;
- @Override
- public void configure(ResourceServerSecurityConfigurer resources) {
- resources.resourceId(RESOURCE_ID).stateless(false)
- .authenticationEntryPoint(restAuthenticationEntryPoint);
- }
- @Override
- public void configure(HttpSecurity http) throws Exception {
- http.authorizeRequests()
- .antMatchers("/admin/**").hasAnyRole("admin","ADMIN");
- http.
- anonymous().disable()
- .authorizeRequests()
- .antMatchers("/users/**").access("hasRole('ADMIN')")
- .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
- //http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);
- http.exceptionHandling().accessDeniedHandler(restAccessDeniedHandler);
- }
- }
资源服务器的配置
4,RestAccessDeniedHandler.java
- @Component("restAccessDeniedHandler")
- public class RestAccessDeniedHandler implements AccessDeniedHandler {
- //处理权限不足的情况:403,对应:access_denied
- @Override
- public void handle(HttpServletRequest request, HttpServletResponse response,
- AccessDeniedException accessDeniedException)
- throws IOException, ServletException {
- System.out.println("-------RestAccessDeniedHandler");
- ServletUtil.printRestResult(RestResult.error(403,"权限不够访问当前资源,被拒绝"));
- }
- }
遇到access deny情况的处理
5,RestAuthenticationEntryPoint.java
- @Component("restAuthenticationEntryPoint")
- public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
- //返回未得到授权时的报错:对应:invalid_token
- @Override
- public void commence(
- HttpServletRequest request,
- HttpServletResponse response,
- AuthenticationException authException) throws IOException {
- //System.out.println("commence");
- ServletUtil.printRestResult(RestResult.error(401,"未得到授权"));
- }
- }
匿名用户无权访问时的处理
6,JwtTokenConfig.java
- @Configuration
- public class JwtTokenConfig {
- @Bean
- public TokenStore jwtTokenStore(){
- return new JwtTokenStore(jwtAccessTokenConverter());
- }
- //使用Jwt来作为token的生成
- @Bean
- public JwtAccessTokenConverter jwtAccessTokenConverter(){
- JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
- accessTokenConverter.setSigningKey("internet_plus");
- return accessTokenConverter;
- }
- @Bean
- public TokenEnhancer jwtTokenEnhancer(){
- return new JwtTokenEnhancer ();
- }
- }
配置jwttoken,指定了signkey
7,JwtTokenEnhancer.java
- public class JwtTokenEnhancer implements TokenEnhancer {
- @Override
- public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
- Map<String,Object> info = new HashMap<>();
- info.put("provider","haolarn");
- //设置附加信息
- ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info);
- return null;
- }
- }
返回token信息中的附加信息
8,OauthController.java
- @RestController
- @RequestMapping("/oauth")
- public class OauthController {
- @Autowired
- private TokenEndpoint tokenEndpoint;
- //自定义返回信息添加基本信息
- @PostMapping("/token")
- public RestResult postAccessTokenWithUserInfo(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
- OAuth2AccessToken accessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
- Map<String, Object> data = new LinkedHashMap();
- data.put("accessToken", accessToken.getValue());
- data.put("token_type", accessToken.getTokenType());
- data.put("refreshToken", accessToken.getRefreshToken().getValue());
- data.put("scope", accessToken.getScope());
- data.put("expires_in", accessToken.getExpiresIn());
- data.put("jti", accessToken.getAdditionalInformation().get("jti"));
- return RestResult.success(data);
- }
- }
格式化生成token时的返回json信息
9,其他非关键代码可以访问github查看,不再一一贴出
五,测试效果
1,得到基于jwt的token
访问:http://127.0.0.1:8080/oauth/token
返回结果:
2,用得到的access_token访问admin/hello:查看当前用户和role:
- http://127.0.0.1:8080/admin/hello
返回:
3,换一个无权限的账号登录:
访问admin/hello时会提示无权限
六,查看spring boot的版本:
- . ____ _ __ _ _
- /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
- ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
- \\/ ___)| |_)| | | | | || (_| | ) ) ) )
- ' |____| .__|_| |_|_| |_\__, | / / / /
- =========|_|==============|___/=/_/_/_/
- :: Spring Boot :: (v2.3.3.RELEASE)
spring boot:spring security实现oauth2+jwt管理认证授权及oauth2返回结果格式化(spring boot 2.3.3)的更多相关文章
- [认证授权] 2.OAuth2授权(续) & JWT(JSON Web Token)
1 RFC6749还有哪些可以完善的? 1.1 撤销Token 在上篇[认证授权] 1.OAuth2授权中介绍到了OAuth2可以帮我们解决第三方Client访问受保护资源的问题,但是只提供了如何获得 ...
- [认证授权] 2.OAuth2(续) & JSON Web Token
0. RFC6749还有哪些可以完善的? 0.1. 撤销Token 在上篇[认证授权] 1.OAuth2授权中介绍到了OAuth2可以帮我们解决第三方Client访问受保护资源的问题,但是只提供了如何 ...
- Spring Cloud实战 | 最终篇:Spring Cloud Gateway+Spring Security OAuth2集成统一认证授权平台下实现注销使JWT失效方案
一. 前言 在上一篇文章介绍 youlai-mall 项目中,通过整合Spring Cloud Gateway.Spring Security OAuth2.JWT等技术实现了微服务下统一认证授权平台 ...
- asp.net core 3.1多种身份验证方案,cookie和jwt混合认证授权
开发了一个公司内部系统,使用asp.net core 3.1.在开发用户认证授权使用的是简单的cookie认证方式,然后开发好了要写几个接口给其它系统调用数据.并且只是几个简单的接口不准备再重新部署一 ...
- Spring cloud微服务实战——基于OAUTH2.0统一认证授权的微服务基础架构
https://blog.csdn.net/w1054993544/article/details/78932614
- [认证授权] 1.OAuth2授权
1 OAuth2解决什么问题的? 举个栗子先.小明在QQ空间积攒了多年的照片,想挑选一些照片来打印出来.然后小明在找到一家提供在线打印并且包邮的网站(我们叫它PP吧(Print Photo缩写
- 【Spring Cloud & Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权
一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证授权.鉴权的逻辑,结合 ...
- Spring Security OAuth2.0认证授权五:用户信息扩展到jwt
历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...
- Spring Security OAuth2.0认证授权四:分布式系统认证授权
Spring Security OAuth2.0认证授权系列文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授 ...
随机推荐
- Minimizing maximizer(POJ 1769)
原题如下: Minimizing maximizer Time Limit: 5000MS Memory Limit: 30000K Total Submissions: 5104 Accep ...
- Appium之定位元素
常用的appium元素定位工具: (1)Android SDK 中提供的元素定位工具uiautomatorviewer: (2)AppiumDesktop提供的元素定位工具Appium Inspec ...
- pip更新命令
python -m pip install --upgrade pip 更新时如果报错'NoneType' object has no attribute 'bytes', 解决办法:easy_ins ...
- swift基本数据类型使用-数组使用
目录 数组的使用 1.数组的定义 2.对可变数组的基本操作 3.数组的遍历 4.数组的合并 5. 示例 数组的使用 1.数组的定义 1> 定义不可变数组 2> 定义可变数组 2.对可变数组 ...
- [IDEA]Java:“程序包XXX不存在”问题的三种解决方案
###三种方案 ####01 出现jar包找不到的问题,首先有可能是项目依赖中有些jar没有下载完整 而mvn idea:idea这个命令可以检查并继续下载未下载完整的依赖jar. 在命令行输入mvn ...
- charles常用功能 request和response(简单的操作)
先介绍一个修改request请求参数值的方法吧 第一步: 拷贝完成后还需要配置一下: 先添加一个: 然后下一步: 最后点击OK,就可以开始操作request和response数据了 先修改reques ...
- 有向图的基本算法-Java实现
有向图 有向图同无向图的区别为每条边带有方向,表明从一个顶点至另一个顶点可达.有向图的算法多依赖深度搜索算法. 本文主要介绍有向图的基本算法,涉及图的表示.可达性.检测环.图的遍历.拓扑排序以及强连通 ...
- websocket直接绕过JS加密的方式
目录 websocket--hook 服务端--WebSocketServer.js 客户端注入JS代码 python开端口 get_data.py 文件方式 get_user_id.py 文件方式 ...
- 利用glog打印日志
glog出自互联网豪门google,质量有保证,轻量级,入门简单,功能较全,线程安全.有关glog的打印细节本篇文章不再赘述,网上一大堆的资料,参考:glog日志库使用笔记. glog的托管地址:gi ...
- centos7 下 docker 安装
前提: 目前,CentOS 仅发行版本中的内核支持 Docker. Docker 运行在 CentOS 7 上,要求系统为64位.系统内核版本为 3.10 以上. Docker 运行在 CentOS- ...