SpringBoot整合Security和JWT
转自
https://blog.csdn.net/mengxianglong123/article/details/112463172
1.导入相关的依赖和基础配置
1.1导入依赖
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.4.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jtw-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>
1.2 相关配置
# 生成token的相关配置
jwt:
header: Authorization #请求头
base64Secret: wsl_ #盐值
tokenValidityInSeconds: 3600000 #过期时间 10秒
token配置类
@Component
@Data
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {
/** Request Headers : Authorization */
private String header;
/** Base64对该令牌进行编码 */
private String base64Secret;
/** 令牌过期时间 此处单位/毫秒 */
private Long tokenValidityInSeconds;
}
实现security中的UserDetails类,方便后续使用
@Data
public class JwtUser implements UserDetails { //实现UserDeails接口
//用户名
private String username;
//密码
private String password;
// 权限(角色)列表
Collection<? extends GrantedAuthority> authorities;
public JwtUser(String stuId, String password, List<GrantedAuthority> grantedAuthorities) {
this.username = stuId;
this.password = password;
this.authorities = grantedAuthorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
token 生成工具,用来生成和解析token
@Component
public class JWTUtils {
@Resource
private JwtProperties jwtProperties;
/**
* 生成token
* @param name 用户名称
* @return
*/
public String generateToken(String name){
Map<String,Object> map = new HashMap<>();
map.put("username",name);
// map.put("password",user.getPassword());
return Jwts.builder()
//设置用户信息
.setClaims(map)
//token过期时间
.setExpiration(new Date(System.currentTimeMillis()+jwtProperties.getTokenValidityInSeconds()))
//设置主题
.setSubject("Wsl_system")
//设置签名
.signWith(SignatureAlgorithm.HS512,jwtProperties.getBase64Secret().getBytes(StandardCharsets.UTF_8))
.compact();
}
/**
* 解析token
* @param token token
*/
public String analysisToken(String token){
Claims body = Jwts.parser()
.setSigningKey(jwtProperties.getBase64Secret().getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(token)
.getBody();
return body.get("username").toString();
}
}
2.创建自定义权限不足处理类
实现AccessDeniedHandler
类,重写里面的handle方法,源码中的注解
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
Map<String,Object> ret = new HashMap<>();
ret.put("code",401);
ret.put("message","权限不足");
ret.put("data",null);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(ret));
}
}
3.创建认证失败处理类
实现AuthenticationEntryPoint
接口重写这个commence方法,用于验证token失败之后会进入到这个commence方法中。源码注释
@Configuration
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
Map<String,Object> ret = new HashMap<>();
ret.put("code",401);
ret.put("message","无效token");
ret.put("data",null);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(ret));
}
}
4.创建登录成功处理器
实现AuthenticationSuccessHandler
接口,用户登录成功之后进入到onAuthenticationSucess方法,主要生成token,将token给到前端。源码注释
@Configuration
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private JWTUtils jwtUtils;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
String name = authentication.getName();
//生成token
String token = jwtUtils.generateToken(name);
//将生成的authentication放入容器中,生成安全的上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
Map<String,Object> ret = new HashMap<>();
ret.put("code",200);
ret.put("message","登录成功");
ret.put("data",token);
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(ret));
}
}
5.创建Token过滤器
继承OncePerRequestFilter
类,重写doFilterInternal
方法。主要作用是用户登录完成之后,拿着token来访问资源,对token的解析
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtUserServiceImpl jwtUserService;
@Autowired
private JwtProperties jwtProperties;
@Autowired
private JWTUtils jwtUtils;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader(jwtProperties.getHeader());
//解析token
try {
String name = jwtUtils.analysisToken(token);
//当token中的username不为空时进行验证token是否是有效的token
if (name != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = jwtUserService.loadUserByUsername(name);
//认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}catch (Exception e){
e.printStackTrace();
}finally {
filterChain.doFilter(request,response);
}
}
}
6.自定义登录逻辑
实现UserDetailsService
接口,重写loadUserByUsername
方法。这里暂时写死了,正常做法应该查询数据库。源码部分太多。
@Component
public class JwtUserServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
//占时写死 admin
if(!"admin".equals(name)){
throw new UsernameNotFoundException("用户名或密码不存在");
}
List<GrantedAuthority> list = new ArrayList<>();
//设置admin角色 角色前面加 ROLE_
list.add(new SimpleGrantedAuthority("ROLE_admin"));
// list.add(new SimpleGrantedAuthority("add"));
return new JwtUser(
name,
passwordEncoder.encode("123"),
list
);
}
}
7.创建登录失败后的处理器
实现AuthenticationFailureHandler
接口重写onAuthenticationFailure
方法。主要登录失败了之后给友好的提示.源码注释如下:
@Configuration
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
Map<String,Object> ret = new HashMap<>();
ret.put("code",401);
ret.put("message","用户名或密码错误");
ret.put("data",null);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(ret));
}
}
8.配置WebSecurityConfigurer类
继承WebSecurityConfigrerAdaper
重写configure(HttpSecurity http)
方法和将AuthenticationManager
注入到Spring中。将上面的处理器和过滤器添加到这个配置类中,以及配置拦截和放行路径
@Configuration
@EnableWebSecurity
//开启注解权限控制
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler;
@Autowired
private LoginFailureHandler loginFailureHandler;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.successHandler(jwtAuthenticationSuccessHandler)
.failureHandler(loginFailureHandler)
.loginProcessingUrl("/login")
//关闭csrf
.and()
.csrf().disable()
// 自定义认证失败类
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
// 自定义权限不足处理类
.accessDeniedHandler(jwtAccessDeniedHandler).and()
//设置无状态登录
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
//过滤登录退出请求
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
.antMatchers("/login/**").permitAll()
//所有请求都需要拦截
.anyRequest()
.authenticated();
//添加token验证过期器,并指定过滤器的类型是UsernamePasswordAuthenticationFilter
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
9编写测试接口
@RequestMapping("/test")
@RestController
public class TestController {
@GetMapping("/get")
public String get(){
return "SUCCESS";
}
@GetMapping("/delete")
//需要admin角色
@PreAuthorize("hasAnyRole('admin')")
public String delete(){
return "操作成功";
}
@PostMapping("/add")
//需要add权限
@PreAuthorize("hasAnyAuthority('add')")
public String add(){
return "操作成功";
}
}
10 测试
先登录
带着token访问
不带token访问
访问没有权限的接口
推荐讲解Security原理比较好的一个博主
https://blog.csdn.net/qq_40794973/category_9356579.html