引入依赖
pom 文件配置
- spring-boot:选用 2.6.4 版本,2.7.x 后不再维护 oauth 模块
- security:引入安全策略,不需固定版本
- oauth:引入 oauth 2.0 相关类,选用 2.2.7.RELEASE,后续版本有很多类被过期
- jwt:引入 jwt 相关类,选用最新的版本
- jaxb:若 jdk 为 8 则不需引入,升到 11 后必须引入
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.6</version>
</dependency>
?
配置资源服务器
oauth 2.0 服务端可以参考文章: Spring Security oauth2.0 服务端
建议 token 使用 JWT,JWT 可以使用算数的方法进行校验,不需要对颁发 token 的服务端发起验证请求
?
配置 JWT
- JwtAccessTokenConverter:配置 JWT 转换器
- signKey:JWT 的签名,需要和生成 JWT 的服务端一致才能校验通过
- TokenStore:token 的持久化方式,使用 JWT 即使用算数方式,不涉及存储;添加 converter,即添加 JWT 的解析参数
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signKey);
return converter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
?
客户端安全配置
- ResourceServerConfigurerAdapter:配置类必须继承此 Adapter
- @EnableResourceServe: OAuth2 资源服务器提供方便的注释,开启后会经过 Spring Security 过滤器,对传入的 OAuth2 令牌对请求进行身份验证
- resources:添加定义好的 tokenStore,添加 resourceId
- resourceId:服务端生成 JWT 时的一个参数,需对应上才能校验成功
- HttpSecurity:配置哪些 url 请求可以不经过安全校验
@Configuration
@EnableResourceServer
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
private static String resourceId = "oauth2-resource";
private static List<String> ignoreUrl = new ArrayList<>();
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
resources.tokenStore(tokenStore());
resources.resourceId(resourceId);
}
@Override
public void configure(HttpSecurity http) throws Exception {
List<String> ignoreUrl = new ArrayList<>();
ignoreUrl.add("/test");
String[] ignoreUrls = ignoreUrl.toArray(new String[ignoreUrl.size()]);
http.authorizeRequests()
.antMatchers(ignoreUrls).permitAll()
.anyRequest().authenticated();
}
}
?
获取 JWT 内容
默认转换器 DefaultUserAuthenticationConverter,JWT 返回的是 user_name 的值,代码如下所示
public class DefaultUserAuthenticationConverter implements UserAuthenticationConverter {
...
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Object principal = map.get(USERNAME);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
if (userDetailsService != null) {
UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
return null;
}
}
通过 SecurityContextHolder 可以获取
@GetMapping("/test/username")
public Result<Object> getUserName() throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return Result.success("成功!", authentication);
}
?
获取 JWT 实体
通过上述已知,默认情况 JWT 只返回用户的用户名,但实际情况我们希望返回服务端传入 JWT 的实体,即实现 UserDetails 接口的类
自定义用户身份验证转换器
- 自定义转换器,继承 DefaultUserAuthenticationConverter
- 在 UsernamePasswordAuthenticationToken 赋值 principal 为 User 实体
- User 是 org.springframework.security.core.userdetails 的实现类
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
String userName = (String) map.get(USERNAME);
User principal = new User( userName,"N/A", new ArrayList<>());
return new UsernamePasswordAuthenticationToken(principal, "N/A", new ArrayList<>());
}
}
把刚新建的转换器添加到 JwtAccessTokenConverter 里
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
CustomUserAuthenticationConverter userAuthenticationConverter = new CustomUserAuthenticationConverter();
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(userAuthenticationConverter);
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signKey);
converter.setAccessTokenConverter(accessTokenConverter);
return converter;
}
获取实体
- User 是 org.springframework.security.core.userdetails 的实现类
@GetMapping("/test/username")
public Result<Object> getUserName() throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
return Result.success("成功!", authentication);
}
|