写在前面:各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!如果我的博客对你有帮助,欢迎进行评论✏️✏️、点赞👍👍、收藏⭐️⭐️,满足一下我的虚荣心💖🙏🙏🙏 。
上一篇我们梳理演示了四种授权模式的过程,不过客户端信息、令牌、用户信息等都存在内存中,本篇将客户端信息、令牌、用户身份信息等都存到Mysql数据库中。
本篇环境及代码是在上一篇基础之上,这里只记录主要的改动部分。
目录
完善环境
配置客户端信息存储
配置令牌存储
配置TokenStore
配置令牌服务
配置授权码存取
配置用户认证授权
配置WebSecurityConfigurerAdapter
配置AuthorizationServerEndpointsConfigurer
测试
授权码模式
简化模式
密码模式
客户端模式
完整代码
授权服务
Web安全
Token配置
完善环境
首先我们需要新建表存储客户端信息、令牌等信息,建表语句可以从如下网站找到:
spring-security-oauth/schema.sql at main · spring-attic/spring-security-oauth · GitHub
每个表的作用可以参考本系列的第一篇 Spring Security OAuth2使用步骤(一)
配置客户端信息存储
首先,我们配置JdbcClientDetailsService使用存取客户端信息,如下:
@Autowired
private DataSource dataSource;
@Bean
public ClientDetailsService clientDetails() {
ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(new BCryptPasswordEncoder());
return clientDetailsService;
}
然后,修改ClientDetailsServiceConfigurer配置,如下:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
配置令牌存储
配置TokenStore
首先,配置TokenStore存储为JdbcTokenStore,如下:
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
配置令牌服务
然后,配置令牌服务,如下:
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setClientDetailsService(clientDetails());
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(tokenStore);
// 配置令牌和刷新令牌的有效期
tokenServices.setAccessTokenValiditySeconds(10);
tokenServices.setRefreshTokenValiditySeconds(10);
return tokenServices;
}
配置授权码存取
对于授权码模式,需要配置授权码的存取,如下:
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
return new JdbcAuthorizationCodeServices(dataSource);
}
配置用户认证授权
对于密码模式,申请令牌时提交的用户名密码需要进行验证,验证通过需要给用户授权,用户信息和用户授权需要一个UserDetailsService实现,并在Web安全配置文件中进行配置。
首选,看下UserDetailsService,如下:
@Component
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_admin");
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(simpleGrantedAuthority);
return new User(username, "$2a$10$bowh1hF9Zwda/O5dUciQde3W6Rk88EP25xC9K9kveFnqFTRFg1n1O", authorities);
}
public static void main(String[] args) {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String password = bCryptPasswordEncoder.encode("123456");
System.out.println(password);
}
}
以上代码仅仅是测试用的,测试数据:用户名随意,密码123456,使用BCryptPasswordEncoder方式加密后为: $2a$10$bowh1hF9Zwda/O5dUciQde3W6Rk88EP25xC9K9kveFnqFTRFg1n1O, 用户的权限设置为ROLE_admin,真正使用的时候用户名密码还有权限信息这些应该是从数据库中查询或者调用远程服务获取的用户相关信息。
注意oauth2在验证权限的时候会在权限字符串前自动加上 “ROLE_”,所以我们构建authorities时需要自己把ROLE_追加上。
配置WebSecurityConfigurerAdapter
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
配置AuthorizationServerEndpointsConfigurer
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
// 密码模式需要
endpoints.authenticationManager(authenticationManager);
// 授权码模式需要
endpoints.authorizationCodeServices(authorizationCodeServices);
// 令牌管理服务
endpoints.tokenServices(tokenServices());
}
至此,基于内存改为数据库的改造就完成了。
测试
下面将测试四种授权模式,每种授权模式的流程及参数参考上一篇 Spring Security OAuth2四种授权模式总结(七)
授权码模式
先清空一下oauth_access_token 、oauth_refresh_token、oauth_code三个表。
浏览器访如下连接:
http://localhost:9005/oauth/authorize?client_id=dev-client&response_type=code&scope=all&redirect_uri=https://www.baidu.com
输入账号:zhangsan 密码:123456,登录进入授权页面:
确认授权后,浏览器重定向到指定路径并附加验证码,code每次都不一样,且用一次就失效,返回的授权码如下:
此时看下数据库oauth_code表,会出现一条记录,如下:
使用该code获取token成功,结果如下:
http://localhost:9005/oauth/token?client_id=dev-client&client_secret=123456&grant_type=authorization_code&code=Lizc1c&redirect_uri=https://www.baidu.com
{
"access_token": "1b365d33-c7b2-42c2-9eb4-6ccf94c63826",
"token_type": "bearer",
"refresh_token": "67b4739b-7d75-4b15-90cb-2a254f267afe",
"expires_in": 9,
"scope": "all"
}
此时看下数据库oauth_access_token 、oauth_refresh_token两个表,分别会出现一条记录,如下:
再看下数据库oauth_code表,原本那条数据已经没有了,因为code用一次就会失效删除,如下:
简化模式
先清空一下oauth_access_token 、oauth_refresh_token两个表。
浏览器访如下连接:
http://localhost:9005/oauth/authorize?client_id=dev-client&response_type=token&scope=all&redirect_uri=https://www.baidu.com
输入账号:zhangsan 密码:123456,登录进入授权页面:
确认授权后,浏览器会重定向到指定的redirect_uri路径,并将token存放在uri路径之后。
此时看下数据库oauth_access_token 、oauth_refresh_token两个表,分别会出现一条记录,如下:
密码模式
先清空一下oauth_access_token 、oauth_refresh_token两个表。
使用Postman访问如下连接:
localhost:9005/oauth/token?client_id=dev-client&client_secret=123456&grant_type=password&username=zhangsan&password=123456
此时看下数据库oauth_access_token 、oauth_refresh_token两个表,分别会出现一条记录,如下:
客户端模式
先清空一下oauth_access_token 、oauth_refresh_token两个表。
使用Postman访问如下连接:
localhost:9005/oauth/token?client_id=dev-client&client_secret=123456&grant_type=client_credentials&scopes=all
此时看下数据库oauth_access_token 、oauth_refresh_token两个表,分别会出现一条记录,如下:
完整代码
授权服务
@EnableAuthorizationServer
@Configuration
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private TokenStore tokenStore;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
// 密码模式需要
endpoints.authenticationManager(authenticationManager);
// 授权码模式需要
endpoints.authorizationCodeServices(authorizationCodeServices);
// 令牌管理服务
endpoints.tokenServices(tokenServices());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
security.checkTokenAccess("permitAll()");
security.tokenKeyAccess("permitAll()");
}
/**
* 配置令牌服务
*/
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setClientDetailsService(clientDetails());
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(tokenStore);
// 配置令牌和刷新令牌的有效期
tokenServices.setAccessTokenValiditySeconds(10);
tokenServices.setRefreshTokenValiditySeconds(10);
return tokenServices;
}
/**
* 配置授权码模式的授权码如何存取
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* 配置客户端信息如何存取
*/
@Bean
public ClientDetailsService clientDetails() {
ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(new BCryptPasswordEncoder());
return clientDetailsService;
}
}
Web安全
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/ignore");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin").hasAuthority("admin")
.antMatchers("/common").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.and().csrf().disable();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
Token配置
@Configuration
public class TokenConfig {
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}