【SpringSecurity】十一、SpringSecurity集成JWT实现token的方法与校验

文章目录

1、依赖与配置

添加JWT的maven依赖:

xml 复制代码
<!-- 添加jwt的依赖 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.11.0</version>
</dependency>

application.yaml中配置密钥的值,方便代码中引用和后续更改:

yaml 复制代码
jwt:
  secretKey: mykey

2、JWT工具类

这里的命名改为JWTService好点,Utils命名似乎偏静态方法一点。

java 复制代码
@Component
@Slf4j
public class JwtUtils {
    //算法密钥
    @Value("${jwt.secretKey}")
    private String jwtSecretKey;

    /**
     * 创建jwt
     *
     * @param userInfo 用户信息
     * @param authList 用户权限列表
     * 根据登录用户的数据库信息和权限信息,加上服务端密钥,创建token
     * @return 返回jwt(JSON WEB TOKEN)
     */
    public String createToken(String userInfo, List<String> authList) {
        //创建时间
        Date currentTime = new Date();
        //过期时间,5分钟后过期
        Date expireTime = new Date(currentTime.getTime() + (1000 * 60 * 5));
        //jwt的header信息
        Map<String, Object> headerClaims = new HashMap<>();
        headerClaims.put("type", "JWT");
        headerClaims.put("alg", "HS256");
        //创建jwt
        return JWT.create()
                .withHeader(headerClaims) // 头部信息
                .withIssuedAt(currentTime) //已注册声明:签发日期,发行日期
                .withExpiresAt(expireTime) //已注册声明 过期时间
                .withIssuer("llg")  //已注册声明,签发人
                .withClaim("userInfo", userInfo) //私有声明,可以自己定义
                .withClaim("authList", authList) //私有声明,可以自定义
                .sign(Algorithm.HMAC256(jwtSecretKey)); // 签名,使用HS256算法签名,并使用密钥
        //HS256是一种对称算法,这意味着只有一个密钥,在双方之间共享。 使用相同的密钥生成签名并对其进行验证。 应特别注意钥匙是否保密。
    }

    /**
     * 验证jwt的签名,简称验签
     * @param token 需要验签的jwt
     * @return 验签结果
     */
    public boolean verifyToken(String token) {
        //获取验签类对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
        try {
            //验签,如果不报错,则说明jwt是合法的,而且也没有过期
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            return true;
        } catch (JWTVerificationException e) {
            //如果报错说明jwt 为非法的,或者已过期(已过期也属于非法的)
            log.error("验签失败:{}", token);
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 从token中获取用户信息
     * 这个userInfo是创建token时我自己塞进去的
     * @param token jwt
     * @return 用户信息
     */
    public String getUserInfo(String token) {
        //创建jwt验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
        try {
            //验签(主要为了同时的获取解析的结果)
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            //获取payload中userInfo的值,并返回
            return decodedJWT.getClaim("userInfo").asString();
        } catch (JWTVerificationException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取用户权限
     *
     * @param token
     * @return
     */
    public List<String> getUserAuth(String token) {
        //创建jwt验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
        try {
            //验签(主要为了同时的获取解析的结果)
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            //获取payload中的自定义数据authList(权限列表),并返回
            return decodedJWT.getClaim("authList").asList(String.class);
        } catch (JWTVerificationException e) {
            e.printStackTrace();
        }
        return null;
    }

}

再贴一下下统一结果类的定义:

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class HttpResult implements Serializable {
    private Integer code; //响应码
    private String msg; //响应消息
    private Object data; //响应对象
}

下面是安全用户类,用于在数据库的用户对象类SysUser和返给框架的官方对象类UserDetails之间做过渡转换。UserDetails <====> SecurityUser <====> SysUser

java 复制代码
@Setter
public class SecurityUser implements UserDetails {

    private  final SysUser sysUser;

	private List<SimpleGrantedAuthority> simpleGrantedAuthorities;

    public SecurityUser(SysUser sysUser) {
        this.sysUser=sysUser;
    }
	public SysUser getSysUser() {
    return sysUser;
}


    @Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
    	return simpleGrantedAuthorities;
	}



    @Override
    public String getPassword() {
        String userPassword=this.sysUser.getPassword();
		//注意清除密码
		this.sysUser.setPassword(null);
		return userPassword;

    }

    @Override
    public String getUsername() {
        return sysUser.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return sysUser.getAccountNoExpired().equals(1);
    }

    @Override
    public boolean isAccountNonLocked() {
        return sysUser.getAccountNoLocked().equals(1);
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return sysUser.getCredentialsNoExpired().equals(1);
    }

    @Override
    public boolean isEnabled() {
        return sysUser.getEnabled().equals(1);
    }
}

3、认证成功处理器

自定义处理器,实现AuthenticationSuccessHandler,当用户登录认证成功后,会执行这个处理器,即认证成功处理器

java 复制代码
/**
 * 认证成功处理器,当用户登录成功后,会执行此处理器
 */
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    //使用此工具类进行序列化
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private JwtUtils jwtUtils;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //从认证对象中获取认证用户信息
		//查看前面第六章的UserDetailsService接口的loadUserByUsername方法,
		//返回给框架的是一个自定义的SecurityUser对象(Security实现了UserDetails)
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        //从SecurityUser中拿出和底层MYSQL挂钩的SysUser类信息
        String userInfo=objectMapper.writeValueAsString(securityUser.getSysUser());
        List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) securityUser.getAuthorities();
        //List<SimpleGrantedAuthority>转List<String>
        List<String> authList=new ArrayList<>();
        for (SimpleGrantedAuthority authority : authorities) {
            authList.add(authority.getAuthority());
        }
		//也可使用stream流代替上面的for循环
		List<String> authList = authorities.stream().map(
		        a -> {
		           return a.getAuthority();
		        }
		).collect(Collectors.toList());

		//也可使用stream流+Lambda表达式
		List<String> authList = authorities.stream()
		.map(SimpleGrantedAuthority::getAuthority)
		.collect(Collectors.toList());


        // 调用前面的JWT工具类方法创建jwt
        String token = jwtUtils.createToken(userInfo,authList);

        //返回给前端token@Builder模式创建对象
        HttpResult httpResult = HttpResult.builder().code(200).msg("OK").data(token).build();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(objectMapper.writeValueAsString(httpResult));
        writer.flush();
    }
}

4、创建JWT过滤器

定义JWT过滤器,用来检验一个个对接口的请求中token是否合法,注意放行登录接口:

java 复制代码
/**
 * 定义一次性请求过滤器
 */
@Component
@Slf4j
public class JwtCheckFilter extends OncePerRequestFilter {
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private JwtUtils jwtUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取请求uri
        String requestURI = request.getRequestURI();
        // 如果是登录页面,放行
        if (requestURI.equals("/login")) {
            filterChain.doFilter(request, response);
            return;
        }
        //获取请求头中的Authorization,前端一般这么传,key为Authorization
        String authorization = request.getHeader("Authorization");
        //如果Authorization为空,那么不允许用户访问,直接返回
        if (!StringUtils.hasText(authorization)) {
            printFront(response, "没有登录!");
            return;
        }
        //Authorization 去掉头部的Bearer 信息,获取token值
        String jwtToken = authorization.replace("Bearer ", "");
        //验签
        boolean verifyTokenResult = jwtUtils.verifyToken(jwtToken);
        //验签不成功
        if (!verifyTokenResult) {
            printFront(response, "jwtToken 已过期");
            return;
        }
		//到这儿算是验证通过,但还没结束,还要将信息填充后返给SpringSecurity框架
        //从payload中获取userInfo
        String userInfo = jwtUtils.getUserInfo(jwtToken);
        //从payload中获取授权列表
        List<String> userAuth = jwtUtils.getUserAuth(jwtToken);
        //在认证成功处理器中,创建token时,userInfo里放的是SysUser对象的序列化字符串,这里反序列化
        SysUser sysUser = objectMapper.readValue(userInfo, SysUser.class);
        SecurityUser securityUser = new SecurityUser(sysUser);
        //设置权限
        List<SimpleGrantedAuthority> authList = userAuth.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        securityUser.setAuthorityList(authList);

		//填充信息
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToke = new UsernamePasswordAuthenticationToken(securityUser
                , null, authList);
        //通过安全上下文设置认证信息
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToke);
        //继续访问相应的rul等
        filterChain.doFilter(request, response);

    }
    
    /**
 	* 定义一个通过response向前端返回数据的方法
 	* 这里不是controller层,不是你直接返回个结果类就行的,注意区别
 	*/
    private void printFront(HttpServletResponse response, String message) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        HttpResult httpResult = new HttpResult();
        httpResult.setCode(401);
        httpResult.setMsg(message);

        writer.print(objectMapper.writeValueAsString(httpResult));
        writer.flush();
    }
}

上面的过滤器中,除了正常的验签,最后的消息填充与保存在安全上下文,就是下图中的第十步:

5、安全配置类

修改下安全配置类,把上面的处理器和过滤器加进来。

java 复制代码
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Resource
    private JwtCheckFilter jwtCheckFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	//先看token是否合法,再走框架的用户名密码校验过滤器
        http.addFilterBefore(jwtCheckFilter, UsernamePasswordAuthenticationFilter.class);
        //要是有之间的验证码校验,则它应该在token校验之前
        //认证通过后,走认证成功处理器,颁发token
        http.formLogin().successHandler(myAuthenticationSuccessHandler).permitAll();
        //简单按接口加个权限要求
		http.authorizeRequests()
        .mvcMatchers("/student/**")
        .hasAnyAuthority("student:query","student:update")
		.anyRequest()
		.authenticated(); //任何请求均需要认证(登录成功)才能访问
        http.csrf().disable();  //跨域
        //禁用session方式
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

   
}

效果:

登录认证后返给前端token:

相关推荐
indexsunny3 天前
互联网大厂Java面试实战:从Spring Boot到微服务架构的技术问答解析
java·spring boot·redis·微服务·kafka·jwt·flyway
好好研究4 天前
SpringBoot扩展SpringMVC
java·spring boot·spring·servlet·filter·listener
我是阿亮啊5 天前
Android Handler 机制完全解析
android·handler·handler机制
知识即是力量ol5 天前
一次完整的 Spring Security JWT 鉴权链路解析
java·后端·spring·鉴权·springsecurity
我是阿亮啊5 天前
Android Handler 消息机制之 Looper 深度解析
android·loop·handler·looper
Jack_David6 天前
Java如何生成Jwt之使用Hutool实现Jwt
java·开发语言·jwt
云游云记7 天前
php JWT 使用全攻略(firebase/php-jwt 实践笔记)
php·jwt
云游云记9 天前
php Token 主流实现方案详解
开发语言·php·token
短剑重铸之日9 天前
《SpringCloud实用版》统一认证授权:Spring Authorization Server + OAuth2 + JWT 生产级方案
java·后端·spring·jwt·oauth2
编程彩机10 天前
互联网大厂Java面试:从Spring Security到微服务架构场景解析
kafka·spring security·微服务架构·jwt·java面试·分布式追踪