我的框架开发记录--2022.4.16

序言

断更的这几天,

一是公司的事情稍微有点多,回家晚一点就只看了看Vue的视频,

二是,在研究security如何使用token,以及oss的一些东西(新添加了一个文件模块)。

也是乘着今天周六,花了一上午把基于session的方式改为了基于token、封装redis工具类、token工具类。

下午也没干啥,睡了一觉、玩了一把大乱斗,拿快递吃饭。

Vue的话,今天能看到50p,一共170P,按照这个进度还需要两三周。

等看到vue脚手架的时候,我应该就会开始搭建前端的框架了

XpStart–2022.4.16

新增了一个文件模块

打算先整合oss,不过现在对于oss过期时间访问还研究得不是很明白

实现Security + jwt

实现了security+token的方式。

我想的是就不采用以配置的方式来决定使用session还是token了。直接使用token,毕竟session有局限性

使用自定义的token过滤器继承BasicAuthenticationFilter

public class JwtTokenFilter extends BasicAuthenticationFilter {
    
    

    @Autowired
    private TokenUtil tokenUtil;

    @Autowired
    private RedisUtil redisUtil;

    public JwtTokenFilter(AuthenticationManager authenticationManager) {
    
    
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    
    

        // 获取请求头的token
        String token = request.getHeader(tokenUtil.getHeader());
        // token为空,有可能是访问放行或可匿名的资源,让它继续走
        if (StringUtils.isEmpty(token)) {
    
    
            chain.doFilter(request, response);
            return;
        }
        // 验证token合法性,并取出值
        try {
    
    
            tokenUtil.verifyToken(token);
            String userName = tokenUtil.getUserName(token);
            String key = RedisKeyPrefixConstants.USER_TOKEN_PREFIX + userName;
            if (! redisUtil.hasKey(key)) {
    
    
                // token 失效,请重新登录
                ResponseData error = ResponseData.error("身份过期,请重新登录", HttpStatusConstants.UNAUTHORIZED);
                String s = new ObjectMapper().writeValueAsString(error);
                response.setCharacterEncoding("utf-8");
                response.getWriter().print(s);
                response.setStatus(org.springframework.http.HttpStatus.UNAUTHORIZED.value());
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            }
            else if (token.equals(redisUtil.getValue(key, new String()))){
    
    
                // todo 通过id去查询权限
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, null, null);
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                chain.doFilter(request, response);
            }
        } catch (Exception e) {
    
    
            // token 无效
            ResponseData error = ResponseData.error("Invalid Token", HttpStatusConstants.UNAUTHORIZED);
            String s = new ObjectMapper().writeValueAsString(error);
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(s);
            response.setStatus(org.springframework.http.HttpStatus.UNAUTHORIZED.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);

        }
        chain.doFilter(request, response);
    }
}

TokenUtil:

我这里使用的是java-jwt的依赖,可能和jjwt的api有些不一样

@Component
public class TokenUtil {
    
    

    @Value("${token.header}")
    private String header;

    @Value("${token.secret}")
    private String secret;

    @Value("${token.expire}")
    private int expire = 24 * 60;


    /**
     * 创建token
     * @param userName
     * @return
     */
    public String createToken(String userName) {
    
    
        // 添加荷载,即要保存到token的用户信息等
        JWTCreator.Builder builder = JWT.create();

        // 设置过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE, expire);

        // 返回token
        return builder.withExpiresAt(instance.getTime()).withSubject(userName).sign(Algorithm.HMAC256(secret));
    }

    /**
     * 验证token,如果token非法,会抛出异常
     * @param token
     */
    public void verifyToken(String token) {
    
    
        JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
    }

    /**
     * 获取token中保存的信息(如果你保存了的话),token非法会抛出异常
     * @param token
     * @return
     */
    public TokenInfo getInfo(String token) {
    
    
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
        String id = verify.getClaim("id").asString();
        String userName = verify.getClaim("userName").asString();
        TokenInfo tokenInfo = new TokenInfo();
        tokenInfo.setId(id);
        tokenInfo.setUserName(userName);

        return tokenInfo;
    }

    /**
     * 验证token是否过期
     * @param token
     * @return true表示过期,false没过期
     */
    public boolean isExpire(String token) {
    
    
        try {
    
    
            Date expiresAt = JWT.decode(token).getExpiresAt();
            Date now = new Date();
            if (expiresAt.before(now)) {
    
    
                return false;
            }
        } catch (JWTDecodeException e) {
    
    
            return true;
        }
        return true;
    }

    /**
     * 获取主体,即用户名
     * @param token
     * @return
     */
    public String getUserName(String token) {
    
    
        return JWT.decode(token).getSubject();
    }

    public String getHeader() {
    
    
        return header;
    }

    public int getExpire() {
    
    
        return expire;
    }
}

因为RedisUtil的方法并没有全面测试过,所以这里先不放出来,等后面没问题了再加上。

我在用户登录成功的处理器中,将生成的token存放在了redis中

为什么要存放在redis中?

设想一下,用户A,在多个浏览器上登录或者一个浏览器上一直登录,那么每登录一次,服务端就会创建一个token,这是不合理的。因此,存放在redis中,可以控制一个用户一个token,也可以用于单点登录。

还有就是,对于一个不友好的用户,我们从系统中将其拉黑或者删除,将token存放在redis中,可以直接让这个token失效。而不会导致非法用户拥有合法的token。用户修改密码的情况同样适用于此。

自定义认证异常处理

对于认证异常,我们只需要告诉"认证失败"或者"登录失败"

@Component
public class AuthenticationException implements AuthenticationEntryPoint {
    
    
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException authException) throws IOException, ServletException {
    
    

        ResponseData result = ResponseData.error("认证失败", HttpStatus.UNAUTHORIZED.value());
        String s = new ObjectMapper().writeValueAsString(result);
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(s);
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    }
}

对于拒绝访问异常,只需要告诉权限不足

@Component
public class AccessHandler implements AccessDeniedHandler {
    
    
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    
    
        ResponseData result = ResponseData.error("权限不足", HttpStatus.FORBIDDEN.value());
        String s = new ObjectMapper().writeValueAsString(result);
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(s);
        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    }
}

好了,继续学习vue去了

修改记录:

2022.4.22:
修改了JwtTokenFilter,昨晚发现之前的token过滤器会拦截到放行的资源。
在JwtTokenFilter中,应该首先判断token为不为空。为空说明访问的可能是放行资源、可匿名访问资源。token为空直接执行下一个过滤器,方法应该直接return结束不做任何处理

猜你喜欢

转载自blog.csdn.net/qq_42682745/article/details/124219414