Spring Security OAuth2 Redis 模式下认证服务器
四种授权码模式
- 授权码模式
- 密码模式
- 客户端模式
- 简化模式
密码模式
grant_type
:授权类型,必选,此处固定值“password”username
:表示用户名,必选password
:表示用户密码,必选scope
:权限范围,可选
(1)获取access_token
http://localhost:9003/oauth/token?username=user&password=password&grant_type=password&client_id=client&client_secret=secret
{
"access_token":"9a263f17f894488fa0973136e6809198",
"token_type":"bearer",
"refresh_token":"1d9b97d9eeb74c62adf9f834868518e3",
"expires_in":59,
"scope":"all",
"client_id":"client_password"
}
(2)通过refresh_token获取access_token
http://localhost:9003/oauth/token?grant_type=refresh_token&refresh_token=1d9b97d9eeb74c62adf9f834868518e3&client_id=client_password&client_secret=secret
{
"access_token":"9229d299bf264b0d8771306d1da71f42",
"token_type":"bearer",
"refresh_token":"1d9b97d9eeb74c62adf9f834868518e3",
"expires_in":59,
"scope":"all",
"client_id":"client_password"
}
授权码模式
client_id
:客户端ID,必选response_type
:必须为code,必选redirect_uri
:回调url,必选
(1)获取授权码
http://localhost:9003/oauth/authorize?client_id=auth_code&response_type=code&redirect_uri=http://localhost:9003/auth_user/get_auth_code
(2)获取access_token
http://localhost:9003/oauth/token?grant_type=authorization_code&code=S4x5Yi&client_id=auth_code&client_secret=secret&redirect_uri=http://localhost:9003/auth_user/get_auth_code
{
"access_token":"46d216c6e19c4761a95c3313883574d7",
"token_type":"bearer",
"refresh_token":"5124987a8e4643088de2ba03bdb24730",
"expires_in":59,
"scope":"select replace insert update del",
"client_id":"auth_code"
}
若报错如下:
{"status":405,"message":"Request method 'GET' not supported"}
参考:https://github.com/lexburner/oauth2-demo/issues/3
@Configuration
public class OAuthSecurityConfig extends AuthorizationServerConfigurerAdapter {
...
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
...
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); //add get method
...
endpoints.tokenServices(tokenServices);
}
...
}
(3)通过refresh_token获取access_token
http://localhost:9003/oauth/token?grant_type=refresh_token&refresh_token=5124987a8e4643088de2ba03bdb24730&client_id=auth_code&client_secret=secret
{
"access_token":"c90e2b29cc2e4b9192e68566148db8cf",
"token_type":"bearer",
"refresh_token":"604baf02b5294daa951fea6fede6a398",
"expires_in":59,
"scope":"select replace insert update del",
"client_id":"auth_code"
}
D:\Server\Redis-x64-3.2.100>redis-cli.exe
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> keys *
1) "refresh:604baf02b5294daa951fea6fede6a398"
2) "access_to_refresh:c90e2b29cc2e4b9192e68566148db8cf"
3) "auth_to_access:a724bdfe1be1f3d30a3c89764c2adca9"
4) "refresh_auth:604baf02b5294daa951fea6fede6a398"
5) "refresh_to_access:604baf02b5294daa951fea6fede6a398"
6) "codeCache"
7) "client_id_to_access:auth_code"
8) "uname_to_access:auth_code:admin"
9) "auth:c90e2b29cc2e4b9192e68566148db8cf"
10) "access:c90e2b29cc2e4b9192e68566148db8cf"
11) "test"
127.0.0.1:6379>
(4)通过refresh_token获取新的access_token时可以自定义用户信息验证service
http://localhost:9003/auth_user/get_token_info?access_token=c90e2b29cc2e4b9192e68566148db8cf
{
"authorities":[
{
"authority":"{"interfaces":["/a/b","/a/c","/oauth/token"]}"
},
{
"authority":"{"username":"admin"}"
}
],
"details":{
"remoteAddress":"0:0:0:0:0:0:0:1",
"sessionId":"8233149F60205EF21BAD74B80BFE7F06"
},
"authenticated":true,
"principal":{
"password":null,
"username":"admin",
"authorities":[
{
"authority":"{"interfaces":["/a/b","/a/c","/oauth/token"]}"
},
{
"authority":"{"username":"admin"}"
}
],
"accountNonExpired":true,
"accountNonLocked":true,
"credentialsNonExpired":true,
"enabled":true
},
"credentials":null,
"name":"admin"
}
Client模式
client_id
: 客户端ID,必选client_secret
: 客户端密码,必选grant_type
: 必须为password,必选scope
: 授权范围,必选
http://localhost:9003/oauth/token?grant_type=client_credentials&scope=insert&client_id=client&client_secret=secret
{
"access_token":"a1016035f36f493391648bb57b1bf6a7",
"token_type":"bearer",
"expires_in":59,
"scope":"insert",
"client_id":"client"
}
极简模式
http://localhost:9003/oauth/authorize?client_id=client_implicit&response_type=token&redirect_uri=http://localhost:9003/auth_user/get_token_info
{
"authorities":[
{
"authority":"{"interfaces":["/a/b","/a/c","/oauth/token"]}"
},
{
"authority":"{"username":"admin"}"
}
],
"details":{
"remoteAddress":"0:0:0:0:0:0:0:1",
"sessionId":"9B935C3B239869DF4A840B57F512F2E7"
},
"authenticated":true,
"principal":{
"password":null,
"username":"admin",
"authorities":[
{
"authority":"{"interfaces":["/a/b","/a/c","/oauth/token"]}"
},
{
"authority":"{"username":"admin"}"
}
],
"accountNonExpired":true,
"accountNonLocked":true,
"credentialsNonExpired":true,
"enabled":true
},
"credentials":null,
"name":"admin"
}
用户认证
/**
* @Description: 用户认证
* @Package: cn.appblog.security.oauth2.service.UserAuthDetailsService
* @Version: 1.0
*/
@Component
public class UserAuthDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 根据用户名查询用户角色、权限等信息
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
GrantedAuthority authority = new UserGrantedAuthority("username", username);
JSONArray array = new JSONArray();
array.add("/a/b");
array.add("/a/c");
array.add("/oauth/token");
GrantedAuthority interfaces = new UserGrantedAuthority("interfaces", array);
/**
isEnabled 账户是否启用
isAccountNonExpired 账户没有过期
isCredentialsNonExpired 身份认证是否是有效的
isAccountNonLocked 账户没有被锁定
*/
return new User(username, passwordEncoder.encode("123456"),
true,
true,
true,
true,
Arrays.asList(authority, interfaces));
}
}
OAuth2授权服务配置
在Security OAuth2授权服务配置类中添加上自定义的用户信息校验类
/**
* 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
DefaultTokenServices tokenServices = new DefaultTokenServices();
//token持久化容器
tokenServices.setTokenStore(tokenStore);
//客户端信息
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
//自定义token生成
tokenServices.setTokenEnhancer(tokenEnhancer());
//access_token 的有效时长 (秒), 默认 12 小时
tokenServices.setAccessTokenValiditySeconds(60 * 1);
//refresh_token 的有效时长 (秒), 默认 30 天
tokenServices.setRefreshTokenValiditySeconds(60 * 2);
//是否支持refresh_token,默认false
tokenServices.setSupportRefreshToken(true);
//是否复用refresh_token,默认为true(如果为false,则每次请求刷新都会删除旧的refresh_token,创建新的refresh_token)
tokenServices.setReuseRefreshToken(false);
endpoints
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) // add get method
//通过authenticationManager开启密码授权
.authenticationManager(authenticationManager)
//自定义refresh_token刷新令牌对用户信息的检查,以确保用户信息仍然有效
.userDetailsService(userAuthDetailsService)
//token相关服务
.tokenServices(tokenServices)
.pathMapping("/oauth/confirm_access", "/custom/confirm_access")
//自定义异常转换处理类
.exceptionTranslator(webResponseExceptionTranslator);
}
自定义token生成
- 自定义一个实现TokenEnhancer接口的token增强器
**
* @Description: 用户自定义token令牌,包括access_token和refresh_token
* @Package: cn.appblog.security.oauth2.enhancer.UserTokenEnhancer
* @Version: 1.0
*/
public class UserTokenEnhancer implements TokenEnhancer {
/**
* 重新定义令牌token
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
if (accessToken instanceof DefaultOAuth2AccessToken) {
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
token.setValue(getToken());
//使用DefaultExpiringOAuth2RefreshToken类生成refresh_token,自带过期时间,否则不生效,refresh_token一直有效
DefaultExpiringOAuth2RefreshToken refreshToken = (DefaultExpiringOAuth2RefreshToken) token.getRefreshToken();
//OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken instanceof DefaultExpiringOAuth2RefreshToken) {
token.setRefreshToken(new DefaultExpiringOAuth2RefreshToken(getToken(), refreshToken.getExpiration()));
}
Map<String, Object> additionalInformation = Maps.newHashMap();
additionalInformation.put("client_id", authentication.getOAuth2Request().getClientId());
//添加额外配置信息
token.setAdditionalInformation(additionalInformation);
return token;
}
return accessToken;
}
/**
* 生成自定义token
*/
private String getToken() {
return StringUtils.join(UUID.randomUUID().toString().replace("-", ""));
}
}
- 将自定义的token增强器加入IOC容器中
/**
* 自定义生成令牌token
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return new UserTokenEnhancer();
}
- 将token增强器加入授权配置端点
/**
* 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
DefaultTokenServices tokenServices = new DefaultTokenServices();
...
tokenServices.setTokenEnhancer(tokenEnhancer());
...
}
自定义token过期时长
/**
* 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
DefaultTokenServices tokenServices = new DefaultTokenServices();
//token持久化容器
tokenServices.setTokenStore(tokenStore);
//客户端信息
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
//自定义token生成
tokenServices.setTokenEnhancer(tokenEnhancer());
//access_token 的有效时长 (秒), 默认 12 小时
tokenServices.setAccessTokenValiditySeconds(60 * 1);
//refresh_token 的有效时长 (秒), 默认 30 天
tokenServices.setRefreshTokenValiditySeconds(60 * 2);
//是否支持refresh_token,默认false
tokenServices.setSupportRefreshToken(true);
//是否复用refresh_token, 默认为true(如果为false, 则每次请求刷新都会删除旧的refresh_token, 创建新的refresh_token)
tokenServices.setReuseRefreshToken(false);
...
}
认证服务器配置 - token存入redis缓存
- 使用Redis缓存需要引入的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 认证服务器配置代码
/**
* @Description: @EnableAuthorizationServer注解开启OAuth2授权服务机制, 优先级顺序order=0
* @Package: cn.appblog.security.oauth2.config.OAuth2ServerConfig
* @Version: 1.0
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigure extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private OAuth2ClientDetailsService oAuth2ClientDetailsService;
@Autowired
private UserAuthDetailsService userAuthDetailsService;
@Autowired
private WebResponseExceptionTranslator webResponseExceptionTranslator;
@Autowired
private TokenStore tokenStore;
@Autowired
private OAuthTokenAuthenticationFilter oAuthTokenAuthenticationFilter;
/**
* 用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里初始化,
* 你可以把客户端详情信息写死也可以写入内存或者数据库中
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//使用自定义ClientDetailsService初始化配置
clients.withClientDetails(oAuth2ClientDetailsService);
}
/**
* 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
DefaultTokenServices tokenServices = new DefaultTokenServices();
//token持久化容器
tokenServices.setTokenStore(tokenStore);
//客户端信息
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
//自定义token生成
tokenServices.setTokenEnhancer(tokenEnhancer());
//access_token 的有效时长 (秒), 默认 12 小时
tokenServices.setAccessTokenValiditySeconds(60 * 1);
//refresh_token 的有效时长 (秒), 默认 30 天
tokenServices.setRefreshTokenValiditySeconds(60 * 2);
//是否支持refresh_token,默认false
tokenServices.setSupportRefreshToken(true);
//是否复用refresh_token,默认为true(如果为false,则每次请求刷新都会删除旧的refresh_token,创建新的refresh_token)
tokenServices.setReuseRefreshToken(false);
endpoints
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) // add get method
//通过authenticationManager开启密码授权
.authenticationManager(authenticationManager)
//自定义refresh_token刷新令牌对用户信息的检查,以确保用户信息仍然有效
.userDetailsService(userAuthDetailsService)
//token相关服务
.tokenServices(tokenServices)
/**
pathMapping用来配置端点URL链接,第一个参数是端点URL默认地址,第二个参数是你要替换的URL地址
上面的参数都是以“/”开头,框架的URL链接如下:
/oauth/authorize:授权端点。对应的类:AuthorizationEndpoint.java
/oauth/token:令牌端点。对应的类:TokenEndpoint.java
/oauth/confirm_access:用户确认授权提交端点。对应的类:WhitelabelApprovalEndpoint.java
/oauth/error:授权服务错误信息端点
/oauth/check_token:用于资源服务访问的令牌解析端点
/oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话
*/
.pathMapping("/oauth/confirm_access", "/custom/confirm_access")
//自定义异常转换处理类
.exceptionTranslator(webResponseExceptionTranslator);
}
/**
* 用来配置令牌端点(Token Endpoint)的安全约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
/**
* 主要是让/oauth/token支持client_id和client_secret做登陆认证
* 如果开启了allowFormAuthenticationForClients,那么就在BasicAuthenticationFilter之前
* 添加ClientCredentialsTokenEndpointFilter,使用ClientDetailsUserDetailsService来进行
* 登陆认证
*/
.allowFormAuthenticationForClients()
//oauth/token端点过滤器
.addTokenEndpointAuthenticationFilter(oAuthTokenAuthenticationFilter);
}
/**
* ApprovalStore用户保存、检索和撤销用户审批的界面
*/
/*
@Bean
public ApprovalStore approvalStore() throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore());
return store;
}
*/
/*
@Bean
public UserApprovalHandler userApprovalHandler1(){
TokenStoreUserApprovalHandler userApprovalHandler = new TokenStoreUserApprovalHandler();
userApprovalHandler.setTokenStore(tokenStore());
return userApprovalHandler;
}
*/
/**
* 自定义生成令牌token
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return new UserTokenEnhancer();
}
}
- Redis缓存配置
##单机应用环境配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=
##Redis数据库索引,默认0
spring.redis.database=0
#spring.redis.timeout=
##redis连接池配置
## 连接池中的最小空闲连接,默认0
spring.redis.jedis.pool.min-idle=0
## 连接池中的最大空闲连接,默认8
spring.redis.jedis.pool.max-idle=8
## 连接池最大阻塞等待时间(使用负值表示没有限制),默认-1ms
spring.redis.jedis.pool.max-wait=-1ms
##连接池最大连接数(使用负值表示没有限制),默认8
spring.redis.jedis.pool.max-active=8
基于Spring Security的安全认证
替换的核心是将
InMemoryTokenStore
对象更换为RedisTokenStore
对象,并传递一个RedisConnectionFactory
接口,接口的具体实现类是JedisConnectionFactory
类;
RedisConnectionFactory可以通过如下三个配置类应用在不同的应用场景:
RedisStandaloneConfiguration:RedisConnectionFactory
工厂类单机模式的配置类RedisSentinelConfiguration:RedisConnectionFactory
工厂类高可用模式的配置类RedisClusterConfiguration:RedisConnectionFactory
工厂类集群模式的配置类
/**
* @Description: 启动基于Spring Security的安全认证, 优先级顺序order=100
* @Package: cn.appblog.security.oauth2.config.WebSecurityConfigurer
* @Version: 1.0
*/
@Configuration
@EnableWebSecurity(debug = true)
@Order(2)
public class BaseSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private UserAuthDetailsService authUserDetailsService;
@Autowired
private UserAuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private UserAccessDeniedHandler accessDeniedHandler;
@Autowired
private UserLogoutSuccessHandler logoutSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http
// .requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**", "/test/**")
// .and()
// .authorizeRequests()
// .antMatchers("/oauth/**").authenticated()
// .and()
// .formLogin().loginPage("/test/login").permitAll(); //新增login form支持用户登录及授权
.requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**", "/test/**")
.and()
// 指定支持基于表单的身份验证, 如果未指定FormLoginConfigurer#loginPage(String), 则将生成默认登录页面
.formLogin()
// 自定义登录页url, 默认为/login
.loginPage("/test/login")
// 登录请求拦截的url, 也就是form表单提交时指定的action
.loginProcessingUrl("/user/login")
// 用户名的请求字段 username
.usernameParameter("username")
// 密码的请求字段 默认为password
.passwordParameter("password")
// 登录成功
// .successHandler(authenticationSuccessHandler)
// 登录失败
.failureHandler(authenticationFailureHandler)
// 无条件允许访问
.permitAll()
.and()
.requestMatchers()
.anyRequest()
.and()
.authorizeRequests()
.antMatchers("/oauth/**")
.authenticated()
//.permitAll()
/* .and()
// 其它的请求要求必须有身份认证
.authorizeRequests()
.anyRequest()
.authenticated()
*/
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll()
.and()
// 认证过的用户访问无权限资源时的处理
.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
//忽略swagger访问权限限制
// web.ignoring().antMatchers(
// "/userlogin",
// "/userlogout",
// "/userjwt",
// "/v2/api-docs",
// "/swagger-resources/configuration/ui",
// "/swagger-resources",
// "/swagger-resources/configuration/security",
// "/swagger-ui.html",
// "/css/**",
// "/js/**",
// "/images/**",
// "/webjars/**",
// "**/favicon.ico",
// "/index");
super.configure(web);
}
/**
* Spring Security认证服务中的相关实现重新定义
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定义的安全认证
auth.userDetailsService(this.authUserDetailsService)
.passwordEncoder(this.passwordEncoder())
.and()
.authenticationProvider(smsAuthenticationProvider())
.authenticationProvider(authenticationProvider());
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* Spring security认证Bean
*/
@Bean
public AuthenticationProvider authenticationProvider() {
AuthenticationProvider authenticationProvider = new UserAuthenticationProvider();
return authenticationProvider;
}
@Bean
public AuthenticationProvider smsAuthenticationProvider() {
AuthenticationProvider authenticationProvider = new UserSmsAuthenticationProvider();
return authenticationProvider;
}
/**
* 自定义加密
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* OAuth2 token持久化接口
*/
@Bean
public TokenStore tokenStore() {
//token保存在内存中(也可以保存在数据库、Redis中)
//如果保存在中间件(数据库、Redis),那么资源服务器与认证服务器可以不在同一个工程中
//注意:如果不保存access_token,则没法通过access_token取得用户信息
//return new InMemoryTokenStore();
return new RedisTokenStore(redisConnectionFactory);
}
}
本文转载参考 原文 并加以调试
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/19/spring-security-oauth2-authentication-server-in-redis-mode/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论