使用JWT实现单点登录

1 什么是JWT

SON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
虽然JWT可以加密以在各方之间提供保密,但只将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则隐藏其他方的声明。当使用公钥/私钥对签署令牌时,签名还证明只有持有私钥的一方是签署私钥的一方。

通俗来讲,JWT是一个含签名并携带用户相关信息的加密串,页面请求校验登录接口时,请求头中携带JWT串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改。校验通过则认为是可靠的请求,将正常返回数据。

2 JWT的使用场景

2.1 授权:这是最常见的使用场景,解决单点登录问题。因为JWT使用起来轻便,开销小,服务端不用记录用户状态信息(无状态),所以使用比较广泛;
2.2 息交换:JWT是在各个服务之间安全传输信息的好方法。因为JWT可以签名,例如,使用公钥/私钥对儿 - 可以确定请求方是合法的。此外,由于使用标头和有效负载计算签名,还可以验证内容是否未被篡改。

JWT的结构

header(头信息)
由两部分组成,令牌类型(即:JWT)、散列算法(HMAC、RSASSA、RSASSA-PSS等)

Payload(有效载荷)
JWT的第二部分是payload,其中包含claims。claims是关于实体(常用的是用户信息)和其他数据的声明,claims有三种类型: registered, public, and private claims。
Registered claims: 这些是一组预定义的claims,非强制性的,但是推荐使用, iss(发行人), exp(到期时间), sub(主题), aud(观众)等;
Public claims: 自定义claims,注意不要和JWT注册表中属性冲突,这里可以查看JWT注册表
Private claims: 这些是自定义的claims,用于在同意使用这些claims的各方之间共享信息,它们既不是Registered claims,也不是Public claims。

Signature
要创建签名部分,必须采用编码的Header,编码的Payload,秘钥,Header中指定的算法,并对其进行签名。
例如,如果要使用HMAC SHA256算法

3 JWT的工作原理

在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web Token(即:JWT)。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,不应将令牌保留的时间超过要求。理论上超时时间越短越好。
在某些情况下,这可以作为无状态授权机制。服务器的受保护路由将检查Authorization

header中的有效JWT ,如果有效,则允许用户访问受保护资源。如果JWT包含必要的数据,则可以减少查询数据库或缓存信息。
如果在Authorization header中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie。

4 使用步骤

1 创建JWT工具类,用于创建tokne和解析token

@ConfigurationProperties( prefix = "jwt.config")
public class JwtUtil {

    private String key ;

    private long ttl ;//一个小时

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public long getTtl() {
        return ttl;
    }

    public void setTtl(long ttl) {
        this.ttl = ttl;
    }

    /**
     * 生成JWT
     *
     * @param id
     * @param subject
     * @return
     */
    public String createJWT(String id, String subject, String roles) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder().setId(id)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
        }
        return builder.compact();
    }

    /**
     * 解析JWT
     * @param jwtStr
     * @return
     */
    public Claims parseJWT(String jwtStr){
        return  Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }

}

在配置文件中添加相关配置

jwt:
  config:
    key: XXXX
    ttl: 3600000

2 配置Spring Security

@Configuration
public class CommonSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").permitAll() //设置请求所有路径都不需要权限
                .anyRequest().authenticated()  //表示所有请求都允许通过
                .and().csrf().disable();
    }

    @Bean
    public BCryptPasswordEncoder bCrypt() {
        return new BCryptPasswordEncoder();
    }
}

3 自定义一个Exception并创建对应的controller来处理该异常

public class AuthorizationException extends RuntimeException {
    public  AuthorizationException(){
        super("没有权限");
    }
}

@ControllerAdvice
public class BaseAuthorizationExceptionHandler {

    @ResponseBody
    @ExceptionHandler(value = AuthenticationException.class)
    public Result error(AuthenticationException e){
        return new Result(false, StatusCode.ACCESSERROR, e.getMessage());
    }
}

4 在用户成功登录时返回token

map.put("token", jwtUtil.createJWT(admin.getId(), admin.getLoginname(), role));
map.put("role", role);

5 用户再发送请求时,将token放在header中

6 在服务端使用拦截器获取header中的token并解析
如果解析成功则执行响应的controller,否则抛出异常

public class RoleUtil {

    //get roles from token
    public Object getRoleFromToken(JwtUtil jwtUtil, HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        System.out.printf("jwtstr is " +  token);
        //先验证token是否存在以及格式是否正确
        if (token == null || "".equals(token) || !token.startsWith("Bearer "))
            throw new AuthorizationException();

        //解析token并提取用户角色信息
        try {
            String roles = jwtUtil.parseJWT(token.substring(7)).get("roles").toString();
            return roles;
        } catch (Exception ex) {
            throw new AuthorizationException();
        }
    }
}
@Component
public class AdminJwtInterceptor implements HandlerInterceptor {

    @Autowired
    HttpServletRequest request;

    @Autowired
    JwtUtil jwtUtil;

    @Autowired
    RoleUtil roleUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object roles = roleUtil.getRoleFromToken(jwtUtil, request);
        if(roles.toString().contains("admin") )
            return true;
        else
            throw new AuthorizationException();
    }
}
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Autowired
    AdminJwtInterceptor jwtInterceptor;

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/**/login");
    }
}
发布了22 篇原创文章 · 获赞 6 · 访问量 9754

猜你喜欢

转载自blog.csdn.net/u012712556/article/details/104152534