Springboot使用SpringSecurity+Jwt前后端分离认证

Springboot使用SpringSecurity+Jwt前后端分离认证,第1张

Springboot使用SpringSecurity+Jwt前后端分离认证

springsecurity是一个权限管理框架,由多个过滤器链组成,本次主要使用springsecurity结合jwt生成token进行前后端分离的权限校验。
权限这块使用了传统的RBAC权限控制,由五张表组成。
引入依赖:

		<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

准备工具类:
登录需要的对象:

@Data
public class UserLoginDto {

   private String username;
   private String password;
}

token存储的信息:

@Data
public class UserInfoDto {
    private String username;
    private List<String> perms;
}

jwt工具类:

public class JwtTokenUtils {
    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String SECRET = "HQ";
    public static final String ISS = "echisan";
    // 过期时间3小时
    private static final Long EXPIRATION = 60 * 60 * 3L; 
    private static final String ROLE = "role";

    /**
     * 创建token
     * @param dto 待生成token的对象
     * @return java.lang.String token字符串
     * @author HQ 
     * @date 2021/3/23 16:02
     */
    public static String createToken(UserInfoDto dto) {
        Map map = new HashMap();
        // 将对象的权限转换成json
        map.put(ROLE, JacksonUtil.toJson(dto.getPerms()));
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setClaims(map)
                .setIssuer(ISS)
                .setSubject(dto.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
                .compact();
    }


    /**
     * 获取token中的用户名
     *
     * @param token token
     * @return java.lang.String 用户名
     * @author HQ
     * @date 2021/3/23 16:02
     */
    public static String getUserName(String token) {
        String username;
        try {
            username = getTokenBody(token).getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 获取token中的权限
     *
     * @param token token
     * @return java.lang.String 权限集合的字符串
     * @author HQ
     * @date 2021/3/23 16:01
     */
    public static String getUserRole(String token) {
        return (String) getTokenBody(token).get(ROLE);
    }

    private static Claims getTokenBody(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            e.printStackTrace();
        } catch (UnsupportedJwtException e) {
            e.printStackTrace();
        } catch (MalformedJwtException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        return claims;
    }

    /**
     * 判断token是否过期
     *
     * @param token 待验证token
     * @return boolean true未过期,false过期
     * @author HQ
     * @date 2021/3/23 16:00
     */

    public static boolean isExpiration(String token) {
        try {
            return getTokenBody(token).getExpiration().before(new Date());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return true;
    }

}

开始集成security
实现UserDetailsService接口,重写loadUserByUsername方法。该方法是security的核心方法,返回UserDetails对象。

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名查询用户信息
        SysUser sysUser = sysUserService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
        // 如果未查询到用户信息抛出异常 用户名或密码错误
        if (sysUser == null) {
            throw new UserCustomException(ResponeEnum.USERNAME_OR_PASSWORD_ERROR);
        }
        // 用户存在 查询用户的权限
        // 判断用户是不是超级管理员 
        List<String> perms;
        if ("admin".equals(username)) {
            List<SysMenu> sysMenuList = sysMenuService.findAllCode();
            perms = (sysMenuList.stream().map(SysMenu::getCode).collect(Collectors.toList()));
        } else {
            perms = sysUserService.findPermsByUserName(username);
        }
        // 查询出来的权限是字符串集合 需要构造成GrantedAuthority的集合
        // 构造权限集合
        List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
        for (String perm : perms) {
            roles.add(new GrantedAuthority() {
                @Override
                public String getAuthority() {
                    return perm;
                }
            });
        }
        // 返回UserDetails对象
        return new User(sysUser.getUsername(), sysUser.getPassword(), roles);
    }

BasicAuthenticationFilter重写身份认证过滤器。

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
   public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
       super(authenticationManager);
   }

   /**
    * 过滤请求
    *
    * @param request
    * @param response
    * @param chain
    * @author HQ
    * @date 2021/3/23 16:14
    */
   @Override
   protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
       // 获取请求头的token信息
       String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
       // 如果请求头中没有Authorization信息则直接放行
       if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
           chain.doFilter(request, response);
           return;
       }
       // 如果请求头中有token,则进行解析,并且设置认证信息
       // 验证token是否过期
       if (!JwtTokenUtils.isExpiration(tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, ""))) {

           UsernamePasswordAuthenticationToken authentication = getAuthentication(tokenHeader);
           // 通过上下文设置认证信息
           SecurityContextHolder.getContext().setAuthentication(authentication);
           super.doFilterInternal(request, response, chain);
       } else {
           chain.doFilter(request, response);
           return;
       }

   }

   /**
    * 根据token获取认证信息
    * @param tokenHeader 待验证token
    * @return org.springframework.security.authentication.UsernamePasswordAuthenticationToken 认证信息
    * @author HQ
    * @date 2021/3/23 16:16
    */
   
   private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
       // 替换前缀
       String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
       // 获取token中的username
       String username = JwtTokenUtils.getUserName(token);
       // 如果username不存在 直接抛出异常
       if (StringUtils.isBlank(username)){
           throw new UserCustomException(ResponeEnum.TOKEN_ERROR);
       }
       // 获得权限 添加到权限上去
       String role = JwtTokenUtils.getUserRole(token);
       if (StringUtils.isBlank(role)){
           // 如果role不存在 直接抛出异常
           throw new UserCustomException(ResponeEnum.TOKEN_ERROR);
       }
       // 将字符串转成list
       List<String> list = JacksonUtil.toObject(role, List.class);
       // 构造认证信息需要的GrantedAuthority对象集合
       List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
       for (String aus : list) {
           roles.add(new GrantedAuthority() {
               @Override
               public String getAuthority() {
                   return aus;
               }
           });
       }
       // 返回认证信息 包含用户名权限
       return new UsernamePasswordAuthenticationToken(username, null, roles);
   }

}

AuthenticationEntryPoint实现认证失败的接口 当认证失败的时候返回

@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        log.info("用户没有携带正确的token:{}",e.getMessage());
        SecurityResUtil.write(httpServletResponse, R.fail(ResponeEnum.TOKEN_ERROR));
    }
}
public class SecurityResUtil {
    public static void write(HttpServletResponse response, R r) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(200);
        PrintWriter writer = response.getWriter();
        String res = JacksonUtil.toJson(r);
        writer.write(res);
        writer.flush();
        writer.close();
    }
}

登录实现

/**
     * 登录
     * @param dto 登录信息
     * @return java.lang.String token字符串
     * @author HQ
     * @date 2021/3/23 16:28
     */
    @Override
    public String login(UserLoginDto dto) {
        // 登陆验证
        UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword());
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(token);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 验证通过
        // 查询用户的权限
        // 判断用户是不是超级管理员
        List<String> perms;
        if ("admin".equals(dto.getUsername())) {
            List<SysMenu> sysMenuList = sysMenuService.findAllCode();
            perms = (sysMenuList.stream().map(SysMenu::getCode).collect(Collectors.toList()));
        } else {
            perms = sysUserService.findPermsByUserName(dto.getUsername());
        }
        // 构造返回的数据
        UserInfoDto infoDto = new UserInfoDto();
        infoDto.setPerms(perms);
        infoDto.setUsername(dto.getUsername());
        // 返回token字符串
        return JwtTokenUtils.createToken(infoDto);
    }
@RestController
public class AuthController {
 @Autowired
 private AuthSerivce authSerivce;

 @PostMapping("/login")
 public R login(@RequestBody UserLoginDto dto){
     String token = authSerivce.login(dto);
     return R.success(token);
 }
}

配置security的配置类WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityJwtConfig extends WebSecurityConfigurerAdapter {

   @Autowired
   private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http.cors().and().csrf().disable().authorizeRequests()
               .antMatchers(HttpMethod.OPTIONS, "/**")
               .permitAll()
               .antMatchers("/").permitAll()
               //login 不拦截
               .antMatchers("/login").permitAll()
               .anyRequest().authenticated()
               //授权
               .and()
               // 禁用session
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
       // 使用自己定义的拦截机制,拦截jwt
       http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
               // 授权错误信息处理
               .exceptionHandling()
               // 用户访问资源没有携带正确的token
               .authenticationEntryPoint(jwtAuthenticationEntryPoint);
   }

   @Bean
   public PasswordEncoder passwordEncoder() {
       // 密码加密
       return new BCryptPasswordEncoder();
   }

}

配置完成,登录返回的token在请求是需要加到请求头:
Authorization Bearer +token

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/738589.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-28
下一篇 2022-04-28

发表评论

登录后才能评论

评论列表(0条)

保存