Aeizzz / aeizzz

0 stars 2 forks source link

shiro 接入 jwt #17

Open Aeizzz opened 5 years ago

Aeizzz commented 5 years ago

shiro 接入 jwt

参考renren-fast 后端更改,把 token 存入redis 进行缓存

流程

  1. 使用用户名密码登陆,成功则返回一个token,失败则返回401
  2. 之后的每次访问都需要再请求头中加入token字段
  3. 后台进行token的校验,如果有错误则返回401

token 校验流程

  1. 获取请求头中的token
  2. 根据token 获取username
  3. 根据username 获取redis 中的token 如果不存在则返回401
  4. 更新redis 中的过期时间

依赖

<dependency>
  <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.2.0</version>
</dependency>

配置jwt

public class JWTUtil {

    // 过期时间5分钟
    private static final long EXPIRE_TIME = 5*60*1000;

    /**
     * 校验token是否正确
     * @param token 密钥
     * @param secret 用户的密码
     * @return 是否正确
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     * @return token中包含的用户名
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成签名,5min后过期
     * @param username 用户名
     * @param secret 用户的密码
     * @return 加密的token
     */
    public static String sign(String username, String secret) {
        try {
            Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            // 附带username信息
            return JWT.create()
                    .withClaim("username", username)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }
}

实现Jwttoken

主要用户登陆,验证token

public class JWTToken implements AuthenticationToken {

    // 密钥
    private String token;

    public JWTToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

实现realm

realm 用于处理用户是否合法这一块

看一下实现的 doGetAuthenticationInfo 方法, 目前制作了简单的校验

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String token = (String) authenticationToken.getPrincipal();

        String username = JwtUtils.getUsername(token);

        // redis  查询用户的token  如果过期则 报错
        String red_token = (String) redisTemplate.opsForValue().get(Constant.DEFAULT_TOKEN_KEY + username);
        if (username == null || StringUtils.isEmpty(username) || red_token == null) {
            throw new IncorrectCredentialsException("token失效,请重新登录");
        }

        // 否则更新过期时间

        redisTemplate.opsForValue().set(Constant.DEFAULT_TOKEN_KEY + username, red_token, SecurityConstants.CODE_TIME, TimeUnit.MINUTES);

        UserDetail userDetail = new UserDetail();
        userDetail.setUser(userService.getUserByLoginUserName(username));
        return new SimpleAuthenticationInfo(userDetail, token, getName());
    }

重写filter

我们实现 AuthenticatingFilter 这个类来实现鉴权的方法

/**
 * jwt  过滤器
 */
public class JwtFilter extends AuthenticatingFilter {

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        String token = getRequestToken((HttpServletRequest) request);
        return new JwtToken(token);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

        //获取请求token,如果token不存在,直接返回401
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            String json = new Gson().toJson(Res.makeErrRsp("invalid token"));
            httpResponse.getWriter().print(json);
            return false;
        }
        return executeLogin(request, response);
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
//        String authorization = getRequestToken((HttpServletRequest) request);

        JwtToken token = (JwtToken) createToken(request,response);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(token);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());

    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
        try {
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            String json = new Gson().toJson(Res.makeErrRsp(throwable.getMessage()));
            httpResponse.getWriter().print(json);
        } catch (IOException e1) {

        }
        return false;
    }

    /**
     * 获取请求的token
     */
    private String getRequestToken(HttpServletRequest httpRequest) {
        //从header中获取token
        String token = httpRequest.getHeader("token");

        //如果header中不存在token,则从参数中获取token
        if (StringUtils.isBlank(token)) {
            token = httpRequest.getParameter("token");
        }

        return token;
    }
}

里面有几个主要的方法

  1. createToken 必须要实现的方法 获取请求头的token 来创建出一个JwtToken 对象
  2. onAccessDenied 表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。 也就是访问拒绝是获取请求头中的token 来进行验证
  3. isAccessAllowed 判断当前是否请求跨域,
SplitfireUptown commented 5 years ago

hey,man,I think it's too simple,you just a little brother!