、先了解拦截器在http请求中所占的位置

spring boot.拦截器 springboot拦截器验证token_ci


spring boot.拦截器 springboot拦截器验证token_ajax_02

shiro配置文件:

@Configuration
public class ShiroConfig {
    //配置类的三大属性
    //一、shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        //HashMap<String, Filter> filterHashMap = new HashMap<>(16);
        //filterHashMap.put("jwt", new ShiroFilter());

        Map<String, Filter> filterMap = new HashMap<>(16);
        filterMap.put("jwt", new ShiroFilter());
        bean.setFilters(filterMap);
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro的内置过滤器
        /**
         * anon:无需认证就可访问
         * authc:必须认证了才能访问
         * user:必须拥有,记住我,功能才能使用
         * perms:拥有对某个资源的权限才能访问
         * role:拥有某个角色权限才能访问
         */
        LinkedHashMap<String, String> filterChainMap = new LinkedHashMap<>();
//        filterChainMap.put("/user/add", "authc");
//        filterChainMap.put("/user/update", "authc");
//        可以使用通配符
//        filterChainMap.put("/user/*", "authc");
        filterChainMap.put("/base", "anon");
        filterChainMap.put("/**", "jwt");
        //普通用户权限
        filterChainMap.put("/user/user", "perms[per:user]");
        //管理员权限
        filterChainMap.put("/user/admin", "perms[per:admin]");

        bean.setFilterChainDefinitionMap(filterChainMap);
        bean.setUnauthorizedUrl("/unauthor");
        bean.setLoginUrl("/toLogin");
        return bean;
    }

    //二、DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //三、创建realm 对象,需要自定义类
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }
}

三、自定义ream

public class UserRealm extends AuthorizingRealm {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * Retrieves the AuthorizationInfo for the given principals from the underlying data store.  When returning
     * an instance from this method, you might want to consider using an instance of
     * {@link SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases.
     *
     * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
     * @return the AuthorizationInfo associated with this principals.
     * @see SimpleAuthorizationInfo
     */
//    授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //授权操作
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        Subject subject = SecurityUtils.getSubject();
        //User user = (User) subject.getPrincipal();
        UserInfo user = (UserInfo) subject.getPrincipal();
        UserRole userRolePermission = userRoleService.getUserRolePermission(user.getUserEmail());
        //List list = userService.queryUserPermissionsList(user.getUsername());
        //subject.getPrincipal();
//        利用user对象,获取user对应的相关权限并加入到授权中
//        Iterator iterator = list.iterator();
//        while (iterator.hasNext()) {
//            String next = (String) iterator.next();
//            info.addStringPermission(next);
//        }
        info.addStringPermission(userRolePermission.getPermission());
        info.addStringPermission(userRolePermission.getRole());
        return info;
    }



    /**
     * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
     * authentication token.
     * <p/>
     * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
     * more and letting Shiro do the rest.  But in some systems, this method could actually perform EIS specific
     * log-in logic in addition to just retrieving data - it is up to the Realm implementation.
     * <p/>
     * A {@code null} return value means that no account could be associated with the specified token.
     *
     * @param token the authentication token containing the user's principal and credentials.
     * @return an {@link AuthenticationInfo} object containing account data resulting from the
     * authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
     * @throws AuthenticationException if there is an error acquiring data or performing
     *                                 realm-specific authentication logic for the specified <tt>token</tt>
     */

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    //    认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取当前的用户
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//        封装用户的登录数据
        UserInfo userInfo = userInfoMapper.selectByUserEmail(userToken.getUsername());
        String s = Arrays.toString(userToken.getPassword());
        if (userInfo == null) {
            throw new EventException(HttpStatus.BAD_REQUEST, "用户不存在,请确认账号是否正确!");
        } else if (userInfo.getUserPassword().equals(s)) {
            logger.info("密码校验出错!");
            throw new EventException(HttpStatus.BAD_REQUEST, "密码不匹配,请确认密码正确!");
        }


        //发送认证信息{principal:要义;credentials:证书;realmName:领域名称}
//        new SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
//        如果要把对应的用户传到授权的环节,就要在principal上放置user
        return new SimpleAuthenticationInfo(userInfo, userInfo.getUserPassword(), "");
    }
}

四、自定义过滤器

public class ShiroFilter extends BasicHttpAuthenticationFilter {

    private Logger logger = LoggerFactory.getLogger(this.getClass());


    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //这里只有返回false才会执行onAccessDenied方法,因为
        // return super.isAccessAllowed(request, response, mappedValue);
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) request);
        String login = ((HttpServletRequest) request).getServletPath();

        RedisTemplateService redisTemplateService = SpringUtils.getBean(RedisTemplateService.class);


        //判断是否是通用的/base请求,不需要拦截
        if (StringUtils.isMatch(login)) {
            logger.info("请求路径为:" + login + ",不需要拦截");
            return true;
        }

        //没有token
        if (StringUtils.isEmpty(token)) {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            AjaxResult ajaxResult = AjaxResult.error(HttpStatus.UNAUTHORIZED, "请先登录后再操作!");
            String s = new ObjectMapper().writeValueAsString(ajaxResult);
            response.getWriter().print(s);
            logger.error("请求路径==:" + login + "没有token");
            return false;
        }

        JWTUtil.verify(token);
        //从当前shiro中获得用户信息
        String userEmail = JWTUtil.getUserEmail(token);
        String userToken = redisTemplateService.get(userEmail);
        if (userToken.equals(token)) {
            //TODO 判断token是否需要更新,如果需要就更新(视情况而定)
            //if (JWTUtil.isNeedUpdate(token)) {
            //    String updateToken = JWTUtil.updateToken(token);
            //    redisTemplateService.saveToken(userEmail, updateToken);
            //}
            logger.info("请求路径==:" + login + "通过过滤");
            return true;
        } else {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            AjaxResult ajaxResult = AjaxResult.error(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录!");
            String s = new ObjectMapper().writeValueAsString(ajaxResult);
            response.getWriter().print(s);
            logger.error("请求路径==:" + login + "无效token");
        }
        return false;
    }

    private String getRequestToken(HttpServletRequest request) {
        //默认从请求头中获得token
        return request.getHeader("Token");
    }

    /**
     * Check if a given log record should be published.
     *
     * @param record a LogRecord
     * @return true if the log record should be published.
     */
}

五、引入token的工具类和方法实现

public class JWTUtil {

    //设置的一个密钥
    private static final String USER_SRCRET = "booksalon";

    public static final Date expireTime() {
        //创建一个日历
        Calendar instance = Calendar.getInstance();
        //默认令牌过期时间8小时
        instance.add(Calendar.HOUR, 12);
        return instance.getTime();
    }

    public static String updateToken(String update) {
        try {
            return JWT.create()
                    .withSubject(update)
                    .withExpiresAt(expireTime())
                    .sign(Algorithm.HMAC256(USER_SRCRET));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取token
     *
     * @param u user
     * @return token
     */
//    public static String getToken(UserDetails u) {
//        //创建一个日历
//        Calendar instance = Calendar.getInstance();
//        //默认令牌过期时间8小时
//        instance.add(Calendar.HOUR, 1);
//
//        //创建JWT并在负载中加入用户id,和电话
//        JWTCreator.Builder builder = JWT.create();
//        builder.withClaim("id", u.getUsername());
//                //.withClaim("phone", u.getAuthorities());
//
//        return builder.withExpiresAt(instance.getTime())
//                .sign(Algorithm.HMAC256(USER_SRCRET));
////        return builder.sign(Algorithm.HMAC256(USER_SRCRET));
//    }

    //shiro
    public static String getToken(UserInfo u) {
        //创建一个日历
        Calendar instance = Calendar.getInstance();
        //默认令牌过期时间8小时
        instance.add(Calendar.HOUR, 12);

        //创建JWT并在负载中加入用户邮箱
        JWTCreator.Builder builder = JWT.create();
        builder.withClaim("userEmail", u.getUserEmail());

        try {
            return builder.withExpiresAt(instance.getTime())
                    .sign(Algorithm.HMAC256(USER_SRCRET));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
//        return builder.sign(Algorithm.HMAC256(USER_SRCRET));
    }

    /**
     * 验证token合法性 成功返回token
     */
    public static DecodedJWT verify(String token) throws Exception {
        if (token == null) {
            throw new Exception("token不能为空");
        }
        JWTVerifier build = JWT.require(Algorithm.HMAC256(USER_SRCRET)).build();
        return build.verify(token);
    }

    //获取token中的userEmail
    public static String getUserEmail(String token) throws Exception {
        DecodedJWT verify = verify(token);
        return verify.getClaim("userEmail").asString();
    }

    /**
     * 检查token是否需要更新
     *
     * @param token
     * @return
     */
    public static boolean isNeedUpdate(String token) {
        //获取token过期时间
        Date expiresAt = null;
        try {
            expiresAt = JWT.require(Algorithm.HMAC256(USER_SRCRET))
                    .build()
                    .verify(token)
                    .getExpiresAt();
        } catch (TokenExpiredException e) {
            return true;
        } catch (Exception e) {
            throw new RuntimeException("token验证失败");
        }
        //如果剩余过期时间少于过期时常的一般时 需要更新
        return (expiresAt.getTime() - System.currentTimeMillis()) / 1000 / 60 / 60 < 3;

    }


   /* public static void main(String[] args) {
        DecodedJWT verify = verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTcxMDg1MDAsInVzZXJuYW1lIjoiYWRtaW4ifQ.geBEtpluViRUg66_P7ZisN3I_d4e32Wms8mFoBYM5f0");
        System.out.println(verify.getClaim("password").asString());
    }*/
}

六、用户接入shiro登录,subject是一个全局可用的对象

Subject subject = SecurityUtils.getSubject();
            try {
                UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userInfo.getUserEmail(),
                        StringUtils.passwordMd5(userInfo.getUserPassword()));
                subject.login(usernamePasswordToken);
            } catch (Exception e) {
                return AjaxResult.error(e.getMessage());
            }

需要引入的包:

<!--shiro 鉴权和授权导包-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.1</version>
        </dependency>
        <!--        jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.3.0</version>
        </dependency>