当前位置:   article > 正文

SpringBoot 集成 SpringSecurity+JWT_springboot security jwt

springboot security jwt

SpringSecurity,JWT简介

1.什么是SpringSecurity

SpringSecurity是一个功能强大且高度可定制的身份验证和访问控制框架,提供了完善的认证机制和方法级的授权功能,是一款非常优秀的权限管理框架,他的核心是一组过滤链,不同的功能经由不同的过滤器

2.什么是JWT

Json Web Token(JWT) 是为了在网络应用环境间传递声明而执行的一种基于json的开放标准(RFC 7519),token设计为紧凑且安全,特别适用于分布式站点的单点登录(SSO)场景

jwt实际是一个字符串,由头部,载荷,签名组成,用[.]分隔,在服务器直接根据token取出保存的用户信息,即可对token的可用性进行校验,使得单点登录更简单

SpringBoot 集成 SpringSecurity+JWT步骤

1.项目结构

 2.数据库和实体类以及准备【注意:】为了方便只用了两张表,实体类省略

users表:

  1. DROP TABLE IF EXISTS `users`;
  2. CREATE TABLE `users` (
  3. `user_id` int NOT NULL AUTO_INCREMENT,
  4. `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  5. `user_pwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  6. PRIMARY KEY (`user_id`) USING BTREE
  7. ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

role表:

  1. DROP TABLE IF EXISTS `role`;
  2. CREATE TABLE `role` (
  3. `id` int NOT NULL AUTO_INCREMENT,
  4. `user_id` int NULL DEFAULT NULL,
  5. `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  6. PRIMARY KEY (`id`) USING BTREE
  7. ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

3.创建UserDetailsServiceImpl,编写登录操作

  1. package cn.zb.security.service.impl;
  2. import cn.zb.security.entity.Role;
  3. import cn.zb.security.entity.Users;
  4. import cn.zb.security.service.RoleService;
  5. import cn.zb.security.service.UsersService;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  8. import org.springframework.security.core.userdetails.User;
  9. import org.springframework.security.core.userdetails.UserDetails;
  10. import org.springframework.security.core.userdetails.UserDetailsService;
  11. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  12. import org.springframework.stereotype.Service;
  13. import java.util.ArrayList;
  14. import java.util.List;
  15. @Service
  16. public class UserDetailsServiceImpl implements UserDetailsService {
  17. @Autowired
  18. private UsersService usersService;
  19. @Autowired
  20. private RoleService roleService;
  21. /**
  22. * 用户登录
  23. * @param s 获取到的UserName,
  24. * @return user 返回的是org.springframework.security.core.userdetails.User;
  25. * @throws UsernameNotFoundException
  26. */
  27. @Override
  28. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
  29. if (s==null || "".equals("s")){
  30. throw new RuntimeException("用户不能为空");
  31. }
  32. //根据用户名查询对象
  33. Users user = usersService.findUserByUserName(s);
  34. if (user==null){
  35. throw new RuntimeException("用户不存在");
  36. }
  37. List<SimpleGrantedAuthority> authorities=new ArrayList<>();
  38. //根据userId获取Role对象
  39. List<Role> roles = roleService.findRoleByUserId(user.getUserId());
  40. for (Role role:roles) {
  41. authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
  42. }
  43. //这里没有对用户密码进行判断,将数据库中查到的密码封装到了对象中,在WebSecurityConfig中configure()方法中,User进行了验证,
  44. //前端传递的密码是明文,数据中存的是暗文,需要对前端传递的密码加密,进行验证,加密方法是new BCryptPasswordEncoder().encode("pwd")
  45. return new User(user.getUserName(),user.getUserPwd(),authorities);
  46. }
  47. }

4.创建JwtUtils工具类,做token的生成和校验

  1. package cn.zb.security.util;
  2. import io.jsonwebtoken.Claims;
  3. import io.jsonwebtoken.Jwts;
  4. import io.jsonwebtoken.SignatureAlgorithm;
  5. import java.util.Date;
  6. import java.util.HashMap;
  7. public class JwtUtils {
  8. public static final String TOKEN_HEADER="Authorization"; //token请求头
  9. public static final String TOKEN_PREFIX="Bearer";//token前缀
  10. public static final long EXPIRATION=60*60*1000; //token有效期
  11. public static final String SUBJECT="piconjo"; //签名主题
  12. public static final String HEADER_STRING="Passport"; //配置token在http heads中的键值
  13. public static final String APPSECRET_KEY="piconjo_secret"; //应用密钥
  14. public static final String ROLE_CLAIMS="role"; //角色权限声明
  15. //生成token
  16. public static String createToken(String username,String role){
  17. HashMap<String, Object> map = new HashMap<>();
  18. map.put(ROLE_CLAIMS,role);
  19. String token=Jwts.builder()
  20. .setSubject(username)
  21. .setClaims(map)
  22. .claim("username",username)
  23. .setIssuedAt(new Date())
  24. .setExpiration(new Date(System.currentTimeMillis()+EXPIRATION))
  25. .signWith(SignatureAlgorithm.HS512,APPSECRET_KEY)
  26. .compact();
  27. return token;
  28. }
  29. //校验token
  30. public static Claims checkJWT(String token){
  31. try{
  32. Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
  33. return claims;
  34. }catch (Exception e){
  35. e.printStackTrace();
  36. return null;
  37. }
  38. }
  39. //从Token中获取username
  40. public static String getUsername(String token){
  41. Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
  42. return claims.get("username").toString();
  43. }
  44. //从Token中获取用户角色
  45. public static String getUserRole(String token){
  46. Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
  47. return claims.get("role").toString();
  48. }
  49. //校验Token是否过期
  50. public static boolean isExpiration(String token){
  51. Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
  52. return claims.getExpiration().before(new Date());
  53. }
  54. }

5.创建拦截器JWTAuthenticationFilter

springsercurity对UserDetailsServiceImpl返回的user进行验证,验证成功则生成token,失败则返回失败信息

  1. package cn.zb.security.util;
  2. import com.alibaba.fastjson.JSON;
  3. import org.springframework.security.authentication.*;
  4. import org.springframework.security.core.Authentication;
  5. import org.springframework.security.core.AuthenticationException;
  6. import org.springframework.security.core.GrantedAuthority;
  7. import org.springframework.security.core.userdetails.User;
  8. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  9. import javax.servlet.FilterChain;
  10. import javax.servlet.ServletException;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;
  13. import java.io.IOException;
  14. import java.util.Collection;
  15. public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
  16. private AuthenticationManager authenticationManager;
  17. public JWTAuthenticationFilter (AuthenticationManager authenticationManager) {
  18. this.authenticationManager = authenticationManager;
  19. //默认的登录路径是/login,post请求
  20. super.setFilterProcessesUrl("/api/login");
  21. }
  22. //验证操作, 接受并解析用户凭证
  23. @Override
  24. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
  25. //从输入流中获取到登录的信息
  26. //创建一个token并条用authenticationManager.authenticate 让Spring Security进行验证
  27. return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
  28. request.getParameter("username"),request.getParameter("password")));
  29. }
  30. /**
  31. * 验证【成功】后调用的方法
  32. * 若验证成功 生成token并返回
  33. */
  34. @Override
  35. protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
  36. User user= (User) authResult.getPrincipal();
  37. System.out.println(user.toString());
  38. // 从User中获取权限信息
  39. Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
  40. // 创建Token
  41. String token = JwtUtils.createToken(user.getUsername(), authorities.toString());
  42. // 设置编码 防止乱码问题
  43. response.setCharacterEncoding("UTF-8");
  44. response.setContentType("application/json; charset=utf-8");
  45. // 在请求头里返回创建成功的token
  46. // 设置请求头为带有"Bearer "前缀的token字符串
  47. response.setHeader("token", JwtUtils.TOKEN_PREFIX + token);
  48. // 处理编码方式 防止中文乱码
  49. response.setContentType("text/json;charset=utf-8");
  50. // 将反馈塞到HttpServletResponse中返回给前台
  51. response.getWriter().write(JSON.toJSONString("登录成功"));
  52. }
  53. /**
  54. * 验证【失败】调用的方法
  55. */
  56. @Override
  57. protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
  58. String returnData="";
  59. // 账号过期
  60. if (failed instanceof AccountExpiredException) {
  61. returnData="账号过期";
  62. }
  63. // 密码错误
  64. else if (failed instanceof BadCredentialsException) {
  65. returnData="密码错误";
  66. }
  67. // 密码过期
  68. else if (failed instanceof CredentialsExpiredException) {
  69. returnData="密码过期";
  70. }
  71. // 账号不可用
  72. else if (failed instanceof DisabledException) {
  73. returnData="账号不可用";
  74. }
  75. //账号锁定
  76. else if (failed instanceof LockedException) {
  77. returnData="账号锁定";
  78. }
  79. // 用户不存在
  80. else if (failed instanceof InternalAuthenticationServiceException) {
  81. returnData="用户不存在";
  82. }
  83. // 其他错误
  84. else{
  85. returnData="未知异常";
  86. }
  87. // 处理编码方式 防止中文乱码
  88. response.setContentType("text/json;charset=utf-8");
  89. // 将反馈塞到HttpServletResponse中返回给前台
  90. response.getWriter().write(JSON.toJSONString(returnData));
  91. }
  92. }

6.创建拦截器JWTAuthorizationFilter

对前端传递的请求拦截,取出里面的token做token校验,获取UsernamePasswordAuthenticationToken对象返回springsecurity,进行相关的角色判断

  1. package cn.zb.security.util;
  2. import org.apache.commons.lang3.StringUtils;
  3. import org.springframework.security.authentication.AuthenticationManager;
  4. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  5. import org.springframework.security.core.Authentication;
  6. import org.springframework.security.core.AuthenticationException;
  7. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  8. import org.springframework.security.core.context.SecurityContextHolder;
  9. import org.springframework.security.web.AuthenticationEntryPoint;
  10. import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
  11. import javax.servlet.FilterChain;
  12. import javax.servlet.ServletException;
  13. import javax.servlet.http.HttpServletRequest;
  14. import javax.servlet.http.HttpServletResponse;
  15. import java.io.IOException;
  16. import java.util.ArrayList;
  17. import java.util.List;
  18. public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
  19. private AuthenticationManager authenticationManager;
  20. public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
  21. super(authenticationManager);
  22. }
  23. @Override
  24. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
  25. String tokenHeader=request.getHeader(JwtUtils.TOKEN_HEADER);
  26. //因为设置拦截全部请求,所有这里没有token放行之后是会返回没有登录
  27. if (tokenHeader==null || !tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)){
  28. chain.doFilter(request,response);
  29. return;
  30. }
  31. //若请求头中由token,则调用下面的方法进行解析,并设置认证信息
  32. SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
  33. super.doFilterInternal(request,response,chain);
  34. }
  35. public UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader){
  36. //去掉前缀,获取token字符串
  37. String token = tokenHeader.replace(JwtUtils.TOKEN_PREFIX, "");
  38. //从token中解密获取用户用户名
  39. String username=JwtUtils.getUsername(token);
  40. //从token中解密获取用户角色
  41. String role=JwtUtils.getUserRole(token);
  42. String[] roles = StringUtils.strip(role, "[]").split(",");
  43. List<SimpleGrantedAuthority> authorities=new ArrayList<>();
  44. for (String s : roles) {
  45. authorities.add(new SimpleGrantedAuthority(s));
  46. }
  47. if (username!=null){
  48. //这里像SpringSecurity声明用户角色,做相关操作放行
  49. return new UsernamePasswordAuthenticationToken(username,null,authorities);
  50. }
  51. return null;
  52. }
  53. }

7.创建WebSecurityConfig配置类,对springsecurity进行相关配置

  1. package cn.zb.security.config;
  2. import cn.zb.security.util.JWTAuthenticationEntryPoint;
  3. import cn.zb.security.util.JWTAuthenticationFilter;
  4. import cn.zb.security.util.JWTAuthorizationFilter;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.beans.factory.annotation.Qualifier;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  9. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  10. import org.springframework.security.config.annotation.web.builders.WebSecurity;
  11. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  12. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  13. import org.springframework.security.config.http.SessionCreationPolicy;
  14. import org.springframework.security.core.userdetails.UserDetailsService;
  15. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  16. import org.springframework.security.crypto.password.PasswordEncoder;
  17. import org.springframework.web.cors.CorsConfiguration;
  18. import org.springframework.web.cors.CorsConfigurationSource;
  19. import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
  20. @EnableWebSecurity
  21. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  22. @Autowired
  23. @Qualifier("userDetailsServiceImpl")
  24. private UserDetailsService userDetailsService;
  25. @Override
  26. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  27. auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
  28. }
  29. /**
  30. * 安全配置
  31. */
  32. @Override
  33. protected void configure(HttpSecurity http) throws Exception {
  34. http.cors()
  35. .and()
  36. //跨域伪造请求限制无效
  37. .csrf().disable()
  38. .authorizeRequests()
  39. //访问/data路径下的请求需要admin
  40. .antMatchers("/data/**").hasRole("admin")
  41. //拦截所有请求
  42. .antMatchers("/").authenticated()
  43. //对登录做放行,生成token
  44. .antMatchers("/api/login").permitAll()
  45. .and()
  46. //添加jwt登录拦截器
  47. .addFilter(new JWTAuthenticationFilter(authenticationManager()))
  48. //添加jwt鉴权拦截器
  49. .addFilter(new JWTAuthorizationFilter(authenticationManager()))
  50. .sessionManagement()
  51. //关闭session
  52. .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  53. .and()
  54. //异常处理
  55. .exceptionHandling()
  56. .authenticationEntryPoint(new JWTAuthenticationEntryPoint());
  57. }
  58. //使用BCryptPasswordEncoder密码加密
  59. @Bean
  60. public PasswordEncoder passwordEncoder() {
  61. return new BCryptPasswordEncoder();
  62. }
  63. //跨域配置
  64. @Bean
  65. CorsConfigurationSource corsConfigurationSource(){
  66. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  67. //注册跨域配置
  68. source.registerCorsConfiguration("/**",new CorsConfiguration().applyPermitDefaultValues());
  69. return source;
  70. }
  71. //静态资源放行
  72. @Override
  73. public void configure(WebSecurity web) {
  74. // 设置拦截忽略文件夹,可以对静态资源放行
  75. //web.ignoring().antMatchers("/images/**");
  76. }
  77. }

8.创建Controller类,进行代码测试

我们在WebSecurityConfig 配置过,想要访问/data下的方法需要用户拥有admin角色

  1. package cn.zb.security.controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. @RequestMapping("/data")
  7. public class UserController {
  8. @GetMapping("/test")
  9. private String data() {
  10. return "访问成功";
  11. }
  12. }

9.操作截图

用admin和as两个用户,其中as是没有admin权限的

 

 本文做记录使用,参考了其他博主

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/546360
推荐阅读
相关标签
  

闽ICP备14008679号