一,为什么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)的更多相关文章

  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. 为什么互联网巨头们纷纷使用Git而放弃SVN?(内含Git核心命令与原理总结)

    写在前面 最近发现很多小伙伴对于工作中的一些基本工具的使用还不是很了解,比如:Git这个分布式的代码管理仓库,很多小伙伴就不是很了解,或者说不是很熟悉.甚至有些小伙伴都没听说过Git,就只会用个SVN ...

  2. 数据库连接池设计和实现(Java版本)

    1 前言 数据库连接池是目前系统开发必须面对和考虑的问题,原理并不复杂,主要是减少重复连接数据库的代价:在系统中创建预期数量的数据库连接,并将这些连接以一个集合或类似生活中的池一样管理起来,用到的时候 ...

  3. linux下清空文件内容的3个命令

    1.使用echo "" > file_name,直接将空字符串重定向并覆盖到目标文件 2.使用cat /dev/null > file_name,读取dev目录下的一个 ...

  4. PHP7性能提升原因

    1.存储变量的结构体变小,尽量使结构体里成员共用内存空间,减少引用,这样内存占用降低,变量的操作速度得到提升 2.字符串结构体的改变,字符串信息和数据本身原来是分成两个独立内存块存放,php7尽量将它 ...

  5. oracle使用impdp和expdp导入导出数据

    1. 导出数据 开始导出数据前,要创建一个directory,因为导入时需要指定directory,导出的dump文件和日志会保存在该directory对应的目录下 SQL> create di ...

  6. 使用Java Stream,提取集合中的某一列/按条件过滤集合/求和/最大值/最小值/平均值

    不得不说,使用Java Stream操作集合实在是太好用了,不过最近在观察生产环境错误日志时,发现偶尔会出现以下2个异常: java.lang.NullPointerException java.ut ...

  7. 关于Mybaits

    mybatis 返回多表多字段用 mybatis 返回多表多字段用 resultType=”java.util.Map”轻松解决问题.不用加什么DTO.这样前端要什么字段就返回什么字段.不用在对多余的 ...

  8. .NET Core开源导入导出库 Magicodes.IE 2.3发布

    在2.3这一版本的更新中,我们迎来了众多的使用者.贡献者,在这个里程碑中我们也添加并修复了一些功能.对于新特点的功能我将在下面进行详细的描述,当然也欢迎更多的人可以加入进来,再或者也很期待大家来提is ...

  9. Codeforces1146G. Zoning Restrictions

    Description You are planning to build housing on a street. There are n spots available on the street ...

  10. ARCENGINE 10 开发遇到的一些问题

    许多版友在刚刚使用ArcGIS 10做开发的时候,都会遇到这样那样的问题.在担任实习版主的这一个多月里,看到了这么几个与开发环境相关的问题,重复被提到相当多,于是我就做了这个FAQ.Q:哪儿有10的A ...