一. 概述
SpringSecurity默认用过session保存用户登录状态, 现在都是分布式微服务时代了, 基本都是用token认证了,本demo简单实践下整合SpringSecurity实现Token认证
二. SpringBootDemo
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.1 实现思路
- 屏蔽默认的登录、登出逻辑
- 手写一套登入登出逻辑,登录成功把用户信息存到缓存里并设定有效期,登出则清除缓存
- 写一个过滤器,用来拦截校验请求
2.2 自定义登录登出逻辑: AuthController.java
@Slf4j
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Resource
private AuthenticationManager authenticationManager;
@Resource
private RedisTemplate<String,Object> redisTemplate;
/**
* 登录
*/
@PostMapping("/login")
public ApiResponse login(@Valid @RequestBody LoginRequest loginRequest) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmailOrPhone(), loginRequest.getPassword());
// 尝试对传递的Authentication对象进行身份Authentication ,如果成功,则返回完全填充的Authentication对象(包括授予的权限)。
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 把authentication放到当前线程,便是认证完成
SecurityContextHolder.getContext().setAuthentication(authentication);
String fastUUID = IdUtil.fastUUID();
// 把用户信息存到redis,并设置有效期
redisTemplate.opsForValue().set(fastUUID,authentication.getPrincipal(),6, TimeUnit.HOURS);
return ApiResponse.ofSuccess(Dict.create().set("tokenType","Bearer").set("token",fastUUID));
}
/**
* 登出
* @param request
* @return
*/
@PostMapping("/logout")
public ApiResponse logout(HttpServletRequest request) {
try {
// 清除认证信息
String authorization = getAuthorization(request);
SecurityContextHolder.clearContext();
redisTemplate.delete(authorization);
} catch (SecurityException e) {
throw new SecurityException(Status.UNAUTHORIZED);
}
return ApiResponse.ofStatus(Status.LOGOUT);
}
/**
* 从 request 的 header 中获取 Authorization
*
* @param request 请求
* @return JWT
*/
public String getAuthorization(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}else {
throw new SecurityException(-1,"Token非法");
}
}
}
2.3 Token认证过滤器: TokenAuthenticationFilter.java
@Component
@Slf4j
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Resource
private RedisTemplate<String,Object> redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// 获取AuthorizationToken
String authorization = getAuthorization(request);
// 从缓存中获取用户信息
UserDetails userDetails = (UserDetails)redisTemplate.opsForValue().get(authorization);
Assert.notNull(userDetails,"登录已过期请重新登录");
// 构建AuthenticationToken
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 把AuthenticationToken放到当前线程,表示认证完成
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (SecurityException e) {
ResponseUtil.renderJson(response, e);
}
}
/**
* 从 request 的 header 中获取 Authorization
*
* @param request 请求
* @return JWT
*/
public String getAuthorization(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}else {
throw new SecurityException(-1,"Token非法");
}
}
}
2.4 认证配置 SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(CustomConfig.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenAuthenticationFilter tokenAuthenticationFilter;
@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.cors()
// 关闭 CSRF
.and().csrf().disable()
// 登录行为由自己实现,参考 AuthController#login
.formLogin().disable()
.httpBasic().disable()
// 认证请求
.authorizeRequests()
// 所有请求都需要登录访问
.anyRequest()
.authenticated()
// 登出行为由自己实现,参考 AuthController#logout
.and().logout().disable()
// Session 管理
.sessionManagement()
// 因为使用了JWT,所以这里不管理Session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 异常处理
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler);
// 添加自定义 JWT 过滤器
http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
网友评论