1.pom添加jar包引用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
- resouces 添加秘钥文件和配置修改
随机生成的RSA公钥,私钥保存到对应文件
application.properties增加配置
jwt.private.key=classpath:app.key
jwt.public.key=classpath:app.pub
3.UserDetailsServiceImpl 实现类
实现UserDetailsService接口,主要作用是实现应用自己的用户查询逻辑,和数据库关联获取对应的User,组装UserDetails返回
@Service
@AllArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userDao.selectOne(Wrappers.<User>query().lambda().eq(User::getUserName, userName));
if (user==null){
throw new UsernameNotFoundException(userName);
}
return getUserDetails(user);
}
private UserDetails getUserDetails(User user){
return org.springframework.security.core.userdetails.User.
withUsername(user.getUserName()).
password("{noop}"+user.getPassword()).
authorities("web","app").build();
}
}
4. AuthenticationEntryPointImpl 错误处理类
实现 AuthenticationEntryPoint 接口,主要作用是自定义Spring Security的异常处理,否则异常返回到前端的可读性太差。
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
throw e;
}
}
5. WebSecurityConfig 配置信息
- 继承 WebSecurityConfigurerAdapter,添加@Configuration,@EnableWebSecurity 注解
- 重写config()方法,配置授权相关信息,指定免校验url,指定授权模式为JWT,指定异常处理handler
- 指定provider,管理第1步实现的 UserDetailsService
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
AuthenticationEntryPointImpl authenticationEntryPoint;
@Autowired
UserDetailsServiceImpl userDetailsService;
@Value("${jwt.public.key}")
RSAPublicKey key;
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.csrf((csrf) -> csrf.ignoringAntMatchers("/token"))
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler()).authenticationEntryPoint(authenticationEntryPoint)
);
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
@Bean
public AuthenticationManager getManager(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
ProviderManager manager = new ProviderManager(provider);
return manager;
}
}
6. 定义TokenController 增加获取token的接口
主要定义token的过期时间,scop范围,当前登录的用户信息等,返回token
@RestController
public class TokenController {
@Value("${jwt.private.key}")
RSAPrivateKey key;
@PostMapping("/token")
public String token(Authentication authentication) {
Instant now = Instant.now();
long expiry = 36000L;
// @formatter:off
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.issuer("self")
.issueTime(new Date(now.toEpochMilli()))
.expirationTime(new Date(now.plusSeconds(expiry).toEpochMilli()))
.subject(authentication.getName())
.claim("scope", scope)
.build();
// @formatter:on
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
SignedJWT jwt = new SignedJWT(header, claims);
return sign(jwt).serialize();
}
SignedJWT sign(SignedJWT jwt) {
try {
jwt.sign(new RSASSASigner(this.key));
return jwt;
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
}
7.发起请求获取token
basic base64(username:password) 放在请求头里
发起请求获取token如下图,
8.发起普通请求不带token
发起get请求,url=localhost:8080/user/getUserById?id=2
返回结果,提示没有得到授权
{
"timestamp": "2021-07-09T13:24:33.821+00:00",
"status": 401,
"error": "Unauthorized",
"path": "/user/getUserById"
}
9.发起普通请求带上token
返回正确结果
{
"code": 200,
"data": {
"id": 1,
"userName": "xiaowang",
"name": "小王",
"password": "password",
"age": 20,
"sex": 1,
"status": 1,
"createTime": "2021-06-10T08:50:29.000+00:00"
}
}
总结
- 自此springboot整合sprignsecurity的入门版本就跑通了,是不是很简单呢
- 后续我们会针改版本做深入的优化,增加密码加密、密码校验、token的作废、退出功能等方面
- 功能完善会对springsecurity的原理做一些剖析