本文主要记录如何使用 Spring Security 做授权服务器,简单理解就是颁发 token
pom 文件配置
spring-boot 版本:2.6.4
oauth 版本:2.2.7.RELEASE
注意:spring-boot 2.7 后,oauth service 就不再维护了,下文已此版本进行讨论
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.4version>
<relativePath/>
parent>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.security.oauth.bootgroupId>
<artifactId>spring-security-oauth2-autoconfigureartifactId>
<version>2.2.7.RELEASEversion>
dependency>
实现 UserDetailsService 接口
主要用途:获取用户信息,可从数据库查询
构成参数: 用户名、用户密码、 用户权限,可自定义构造方法
@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String userName) {
User userDetails = new User( userName, "password", new ArrayList<>());
return userDetails;
}
}
实现 AuthenticationProvider 接口
主要用途:校验校验账号密码、用户权限等
@Service("userAuthProvider")
@Slf4j
public class UserAuthProvider implements AuthenticationProvider {
@Autowired
private UserDetailsServiceImpl iUserDetailsService;
@Autowired
private IUserMapper userMapper;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();
String password = (String) authentication.getCredentials();
UserPO userPO = userMapper.selectByName(userName);
if (Optional.ofNullable(userPO).isPresent() && password.equals(userPO.getPassword())) {
UserDetails userDetails = iUserDetailsService.loadUserByUsername(userName);
log.info("登录成功");
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
} else {
throw new DisabledException("报错");
}
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
继承 WebSecurityConfigurerAdapter 类,并添加注解 @Configuration、@EnableWebSecurity
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
@Qualifier("userAuthProvider")
private UserAuthProvider userAuthProvider;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(userAuthProvider);
}
@Override
public void configure(WebSecurity web) {
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/test/**" ).permitAll()
.anyRequest()
.authenticated()
.and()
.csrf()
.disable()
.cors()
;
}
}
oauth2.0 服务点配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
UserDetailsServiceImpl userDetailsService;
...
}
对应于配置 AuthorizationServer 安全认证的相关信息,创建 ClientCredentialsTokenEndpointFilter 核心过滤器
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
}
初始化客户端详情信息,注意:不建议同时使用多种模式
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
clients
.inMemory()
.withClient(CLIENT_ID)
.authorizedGrantTypes("password", "refresh_token")
.scopes("read", "write", "trust")
.resourceIds(RESOURCE_ID)
.secret()
.accessTokenValiditySeconds(Math.toIntExact(TimeUnit.DAYS.toSeconds(30)))
.refreshTokenValiditySeconds(Math.toIntExact(TimeUnit.DAYS.toSeconds(30)));
}
一般来说,建议使用 jdbc 模式,方便增删改 client 信息
数据库添加表 oauth_client_details
CREATE TABLE `oauth_client_details` (
`client_id` VARCHAR ( 256 ) CHARACTER
SET utf8 NOT NULL,
`resource_ids` VARCHAR ( 256 ) CHARACTER
SET utf8 DEFAULT NULL,
`client_secret` VARCHAR ( 256 ) CHARACTER
SET utf8 DEFAULT NULL,
`scope` VARCHAR ( 256 ) CHARACTER
SET utf8 DEFAULT NULL,
`authorized_grant_types` VARCHAR ( 256 ) CHARACTER
SET utf8 DEFAULT NULL,
`web_server_redirect_uri` VARCHAR ( 256 ) CHARACTER
SET utf8 DEFAULT NULL,
`authorities` VARCHAR ( 256 ) CHARACTER
SET utf8 DEFAULT NULL,
`access_token_validity` INT ( 11 ) DEFAULT NULL,
`refresh_token_validity` INT ( 11 ) DEFAULT NULL,
`additional_information` VARCHAR ( 4096 ) CHARACTER
SET utf8 DEFAULT NULL,
`autoapprove` VARCHAR ( 256 ) CHARACTER
SET utf8 DEFAULT NULL,
PRIMARY KEY ( `client_id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8;
JWT 有多种生产策略,以下介绍两种
关键字加密
private static final String SIGNING_KEY = "fat";
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);
return jwtAccessTokenConverter;
}
对称加密
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("jwt.jks"), "key".toCharArray())
.getKeyPair("auth-server");
converter.setKeyPair(keyPair);
return converter;
}
定义 Token 的存储方式
@Bean
public TokenStore tokenStore() {
InMemoryTokenStore tokenStore = new InMemoryTokenStore();
return tokenStore;
}
若使用数据库方式存储即 JdbcTokenStore 时,需要创建 oauth_access_token、oauth_refresh_token 两个表,分别存储 access_token 和 refresh_token
oauth_access_token
CREATE TABLE oauth_access_token (
create_time TIMESTAMP DEFAULT now(),
token_id VARCHAR ( 255 ),
token BLOB,
authentication_id VARCHAR ( 255 ) UNIQUE,
user_name VARCHAR ( 255 ),
client_id VARCHAR ( 255 ),
authentication BLOB,
refresh_token VARCHAR ( 255 )
) ENGINE = INNODB DEFAULT CHARSET = utf8;
oauth_refresh_token
如果客户端的 grant_type 不支持 refresh_token,则不会使用该表
CREATE TABLE oauth_refresh_token (
create_time TIMESTAMP DEFAULT now(),
token_id VARCHAR ( 255 ),
token BLOB,
authentication BLOB
) ENGINE = INNODB DEFAULT CHARSET = utf8;
配置授权服务器端点的属性和增强功能,主要用来来配置令牌(token)的访问端点和令牌服务(token services)
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
endpoints
.authenticationManager(authenticationManager)
.tokenServices(tokenServices)
;
}
JWT 配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter()));
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenEnhancer(enhancerChain);
tokenServices.setTokenStore(tokenStore());
endpoints
.authenticationManager(authenticationManager)
.tokenServices(tokenServices)
;
}
refreshToken 配置
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(false);