后端——jwt+时间戳+责任链模式实现对外接口 统一返回格式|全局异常处理器|shiro自定义过滤器中抛出的异常进行全局处理

目录

TokenService——jwt工具类

责任链模式

创建责任链的认证接口

实现参数校验逻辑

责任链上下文

JwtAuthorizationFilter

ShiroConfig

GlobalExceptionHandler  全局异常处理器

参考连接


jwt+时间戳+责任链模式实现对外接口

需求:系统需要提供对外接口,客户可以通过接口获取我们系统的数据

方案:使用shiro+jwt,用户通过用户名和密码访问我们的登录接口/login获取token,访问其他api时携带token和timestamp(防止重放攻击)(注:对接口安全性要求高的可以另选其他方案)

TokenService——jwt工具类

@Component
public class TokenService {
    private static final Logger log = LoggerFactory.getLogger(TokenService.class);

    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;

    // 令牌有效期(默认30)
    @Value("${token.expireTime}")
    private int expireTime;

    protected static final long MILLIS_SECOND = 1000;

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    private static final long MILLIS_HOUR = 60 * MILLIS_MINUTE;

    /**
     * 创建token
     *
     * @param claims token的payload
     * @return token
     */
    public String createToken(Map<String, Object> claims) {
//        Map<String, Object> claims = new HashMap<>();
//        claims.put(Constants.TOKEN_CLAIM_USERNAME, userLoginInfo.getUserName());
//        claims.put(Constants.TOKEN_CLAIM_PASSWORD, userLoginInfo.getPassWord());
        return Jwts.builder()
                .setClaims(claims)
                //设置token过期时间,默认为2h
                .setExpiration(new Date(System.currentTimeMillis() + expireTime * MILLIS_HOUR))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 解析token
     *
     * @param token
     * @return 存储用户账号密码的claim
     */
    public Claims parseToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException e) {
            Claims claims = new DefaultClaims().setExpiration(new Date(System.currentTimeMillis()));
            return claims;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 校验token
     *
     * @param token
     * @return true 有效, false 无效
     */
    public boolean validateToken(String token) {
        return parseToken(token) != null;
    }

    /**
     * 判断token是否过期
     *
     * @param token
     * @return
     */
    public boolean expired(String token) {
        return parseToken(token).getExpiration().before(new Date());
    }
}

责任链模式

创建责任链的认证接口

接口继承Order,然后责任链的结点实现以下接口后,spring容器注入bean时通过Order对责任链结点进行排序,这样很好的生成了责任链

public interface SecurityVerificationHandler extends Ordered {

    /**
     * 请求校验
     */
    void handler(HttpServletRequest request) throws Exception;
}

实现参数校验逻辑

/**
 * 责任链结点--参数校验
 */
@Component
public class ParamVerificationHandler implements SecurityVerificationHandler {
    @Override
    public void handler(HttpServletRequest request) throws ParamException {

        // 时间戳
//        if(StringUtils.isEmpty((String) request.getHeader(Constants.TIMESTAMP))){
//            throw new ParamException("参数" + Constants.TIMESTAMP + ": " + ErrorConstants.TIMESTAMP_NULL);
//        }

        // token
        if (StringUtils.isEmpty(request.getHeader(Constants.AUTHORIZATION))) {
            throw new ParamException("参数" + Constants.AUTHORIZATION + ": " + ErrorConstants.TOKEN_NULL);
        }
    }



    /**
     * 责任链结点的优先级,间隔尽量大,方便以后扩展
     * @return
     */
    @Override
    public int getOrder() {
        return 10;
    }
}
/**
 * 责任链结点--时间戳校验
 */
//@Component
public class TimestampVerificationHandler implements SecurityVerificationHandler {
    @Override
    public void handler(HttpServletRequest request) throws TimestampException {

        String timestamp = request.getHeader(Constants.TIMESTAMP);

        if(!validateTimestamp(timestamp)){
            throw new TimestampException("参数" + Constants.TIMESTAMP + ": " + ErrorConstants.TIMESTAMP_FORMAT_ERROR);
        }

        long diff = System.currentTimeMillis() - Long.parseLong(timestamp);

        if(diff < 0 || diff > Constants.REPLAY_ATTACK_INTERVAL){
            throw new TimestampException( ErrorConstants.REPLAY_ATTACK);
        }

    }

    /**
     * 判断时间戳格式
     * @param timestamp
     * @return
     */
    public boolean validateTimestamp(String timestamp) {
        long nowTime = System.currentTimeMillis();
        boolean flag = false;
        try {
            long l = Long.parseLong(timestamp);
        } catch (NumberFormatException e) {
            return false;
        }

        if (String.valueOf(nowTime).length() != timestamp.length()) {
            return false;
        }

        return true;
    }

    /**
     * 责任链结点的优先级,间隔尽量大,方便以后扩展
     * @return
     */
    @Override
    public int getOrder() {
        return 20;
    }
}
/**
 * 责任链结点--token校验
 */
@Component
public class TokenVerificationHandler implements SecurityVerificationHandler {

    @Autowired
    private TokenService tokenService;

    @Override
    public void handler(HttpServletRequest request) throws TokenException {

        String token = request.getHeader(Constants.AUTHORIZATION);

        if(!tokenService.validateToken(token)){
            throw new TokenException(ErrorConstants.TOKEN_UNVALIDEATED);
        }

        if(tokenService.expired(token)){
            throw new TokenException(ErrorConstants.TOKEN_EXPIRED);
        }

        Claims claims = tokenService.parseToken(token);
        String username = (String) claims.get(Constants.USERNAME);
        String password = (String) claims.get(Constants.PASSWORD);

        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken userToken = new UsernamePasswordToken(username, password);
        try {
            subject.login(userToken);
        } catch (Exception e) {
            throw new TokenException(ErrorConstants.TOKEN_UNVALIDEATED);
        }

    }

    /**
     * 责任链结点的优先级,间隔尽量大,方便以后扩展
     * @return
     */
    @Override
    public int getOrder() {
        return 30;
    }
}

责任链上下文

/**
 * 责任链上下文
 */
@Component
public class SecurityVerificationChain {

    private final List<SecurityVerificationHandler> securityVerificationHandlers;

    @Autowired
    public SecurityVerificationChain(List<SecurityVerificationHandler> securityVerificationHandlers) {
        this.securityVerificationHandlers = securityVerificationHandlers;
    }

    public void handler(HttpServletRequest request) throws Exception {
        // 责任链结点已按书序排列好,直接遍历,顺序校验
        for (SecurityVerificationHandler handler : securityVerificationHandlers) {
            handler.handler(request);
        }
    }

}

JwtAuthorizationFilter

用于shiro框架的自定义的jwt过滤器,大家可以自定义realm的逻辑,这里不展开了

public class JwtAuthorizationFilter extends AccessControlFilter {

    private SecurityVerificationChain securityVerificationChain = SpringUtils.getBean(SecurityVerificationChain.class);

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {


        // 异常
        Exception ex = null;

        try {
            securityVerificationChain.handler(WebUtils.toHttp(request));
        } catch (Exception e) {
            ex = e;
        }

        // 抛一个异常, 全局异常处理
        if (StringUtils.isNotNull(ex)) {
            HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver) SpringUtils.getBean("handlerExceptionResolver");
            handlerExceptionResolver.resolveException(
                    WebUtils.toHttp(request),
                    WebUtils.toHttp(response),
                    null,
                    ex
            );
            return false;
        }

        return true;

    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        return false;
    }
}

ShiroConfig

记得将jwt过滤器配置到shiro里面哈

/**
 * Shiro配置类
 */
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterBean = new ShiroFilterFactoryBean();

        shiroFilterBean.setSecurityManager(securityManager);

        // 配置自定义过滤器
        Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("jwtFilter", new JwtAuthorizationFilter());
        shiroFilterBean.setFilters(filters);

        //配置路径过滤器  anon 无需登陆验证,直接访问; acthc 访问需要进行登录验证z`
        Map<String, String> filterMap = new LinkedHashMap<>();

        // ......

        filterMap.put("/**", "jwtFilter");

        shiroFilterBean.setFilterChainDefinitionMap(filterMap);

        return shiroFilterBean;
    }

}

GlobalExceptionHandler  全局异常处理器

@RestControllerAdvice
public class GlobalExceptionHandler {

    //日志打印类
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);

    // ParamException异常类
    @ExceptionHandler(ParamException.class)
    public ResponseResult customException(ParamException e){

        //打印日志
        LOGGER.error("catch exception : {}\r\nexception: ",e.getMessage(), e);

        return ResponseResult.error(e.getMessage());
    }

    // ParamException异常类
    @ExceptionHandler(TimestampException.class)
    public ResponseResult customException(TimestampException e){

        //打印日志
        LOGGER.error("catch exception : {}\r\nexception: ",e.getMessage(), e);

        return ResponseResult.error(e.getMessage());
    }



    // token异常类
    @ExceptionHandler(TokenException.class)
    public ResponseResult customException(TokenException e){

        //打印日志
        LOGGER.error("catch exception : {}\r\nexception: ",e.getMessage(), e);

        return ResponseResult.error(401, e.getMessage());
    }

    // 其他异常
    @ExceptionHandler(Exception.class)
    public HttpResult customException(Exception e){

        //打印日志
        LOGGER.error("catch exception : {}\r\nexception: ",e.getMessage(), e);

        //创建响应类,也就是最终传给页面的数据
        HttpResult response = HttpResult.error(e.getMessage().toString());

        return response;
    }

}

参考连接

结语:这个方案实现了简单的token+timestamp+责任链设计的对外接口设计,比较简陋,大家可以尝试更好的解决方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值