【Spring Security】 JWT 示例
【Spring Security】 JWT 示例
Metadata
title: 【Spring Security】 JWT 示例
date: 2023-02-02 17:07
tags:
- 行动阶段/完成
- 主题场景/组件
- 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
- 细化主题/Module/SpringSecurity
categories:
- SpringSecurity
keywords:
- SpringSecurity
description: 【Spring Security】 JWT 示例
【Spring Security】 JWT 示例
SpringSecurity默认用过session保存用户登录状态, 现在都是分布式微服务时代了, 基本都是用token认证了,本demo简单实践下整合SpringSecurity实现Token认证
引入依赖
<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>
方案一
实现思路
- 屏蔽默认的登录、登出逻辑
- 手写一套登入登出逻辑,登录成功把用户信息存到缓存里并设定有效期,登出则清除缓存
- 写一个过滤器,用来拦截校验请求
自定义登录登出逻辑: 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非法");
}
}
}
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非法");
}
}
}
认证配置 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);
}
}
方案二
配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 由于过滤器 比 servelt 先加载 在这里注入一下 负责 TokenAuthenticationTokenFilter 中redisuntity
@Bean
public TokenAuthenticationTokenFilter getTokenFiter(){
return new TokenAuthenticationTokenFilter();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//http.addFilterBefore(new VerCodeFi lter("/Login/Login"), UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(getTokenFiter(), UsernamePasswordAuthenticationFilter.class);
http
.authorizeRequests()
.antMatchers("/Login/**").permitAll() // 放行Login
.anyRequest().authenticated() // 所有请求都需要验证
.and()
.formLogin() // 使用默认的登录页面
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable();// post请求要关闭csrf验证,不然访问报错;实际开发中开启,需要前端配合传递其他参数
}
}
定义token 验证过滤器
public class TokenAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisUtils redisUtils;
public TokenAuthenticationTokenFilter(){
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//1、获取请求头携带的token
String token = request.getHeader("token");
if(!StringUtils.hasText(token)){
//不需要token的路由可以直接放行
filterChain.doFilter(request,response);
return;
}
Object o =redisUtils.get(token);
if (o==null){
response.setStatus(200);
response.setCharacterEncoding("utf-8");
response.getWriter().write(JSON.toJSONString(Result.failed(401,"token 非法","")));
return;
}
Map<String,String> maps=new HashMap<>();
Map Values = JSON.parseObject(o.toString(), maps.getClass());
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(Values.get("role").toString()));
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(new Userdto(), null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response); //放行
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 蝶梦庄生!
评论