资讯专栏INFORMATION COLUMN

Spring-boot、Shrio实现JWT

qylost / 3323人阅读

摘要:截获页面错误处理逻辑关于权限等授权信息,可以直接放到中实现缓存。我认为也是不错的。源码奉上分支温馨提示平时测试代码可能比较乱。如果更好的实现,让我学习,让我我进步,请联系我。

关于验证大致分为两个方面:

用户登录时的验证;

用户登录后每次访问时的权限认证

主要解决方法:使用自定义的Shiro Filter 项目搭建:
这是一个spring-boot 的web项目,不了解spring-boot的项目搭建,请google。

pom.mx引入相关jar

 
    
        org.apache.shiro
        shiro-spring
        ${shiro.version}
    
    
        org.apache.shiro
        shiro-core
        ${shiro.version}
    
 
     
        io.jsonwebtoken
        jjwt
        0.9.0
    

Shrio 的相关配置

划重点!!自定义了一个Filter

filterMap.put("JWTFilter", new JWTFilter());

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 添加自己的过滤器并且取名为JWTFilter
        Map filterMap = new HashMap<>();
        filterMap.put("JWTFilter", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        /*
         * 自定义url规则
         * http://shiro.apache.org/web.html#urls-
         */
        Map filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
        filterChainDefinitionMap.put("/**", "JWTFilter");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    /**
     * securityManager 不用直接注入shiroDBRealm,可能会导致事务失效
     * 解决方法见 handleContextRefresh
     * http://www.debugrun.com/a/NKS9EJQ.html
     */
    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(TokenRealm tokenRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(tokenRealm);
        /*
         * 关闭shiro自带的session,详情见文档
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);
        return manager;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean(name = "TokenRealm")
    @DependsOn("lifecycleBeanPostProcessor")
    public TokenRealm tokenRealm() {
        return new TokenRealm();
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return new AuthorizationAttributeSourceAdvisor();
    }
}

自定义Shrio filter

执行顺序:preHandle -> doFilterInternal -> executeLogin -> onLoginSuccess
主要判断是不是登录请求的是 doFilterInternal

public class JWTFilter extends BasicHttpAuthenticationFilter {

    /**
     * 自定义执行登录的方法
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        UsernamePasswordToken usernamePasswordToken = JSON.parseObject(httpServletRequest.getInputStream(), UsernamePasswordToken.class);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        Subject subject = this.getSubject(request, response);
        subject.login(usernamePasswordToken);
        return this.onLoginSuccess(usernamePasswordToken, subject, request, response);
        //错误抛出异常
    }

    /**
     * 最先执行的方法
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        return super.preHandle(request, response);
    }

    /**
     * 登录成功后登录的操作
     * 加上jwt 的header
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        String jwtToken = Jwts.builder()
                .setId(token.getPrincipal().toString())
                .setExpiration(DateTime.now().plusMinutes(30).toDate())
                .signWith(SignatureAlgorithm.HS256, JWTCost.signatureKey)
                .compact();
        httpServletResponse.addHeader(AUTHORIZATION_HEADER, jwtToken);
        return true;
    }

    /**
     * 登录以及校验的主要流程
     * 判断是否是登录,或者是登陆后普通的一次请求
     */
    @Override
    public void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;

        String servletPath = httpServletRequest.getServletPath();
        if (StringUtils.equals(servletPath, "/login")) {
            //执行登录
            this.executeLogin(servletRequest, servletResponse);
        } else {
            String authenticationHeader = httpServletRequest.getHeader(AUTHORIZATION_HEADER);
            if (StringUtils.isNotEmpty(authenticationHeader)) {

                Claims body = Jwts.parser()
                        .setSigningKey(JWTCost.signatureKey)
                        .parseClaimsJws(authenticationHeader)
                        .getBody();
                if (body != null) {
                    //更新token
                    body.setExpiration(DateTime.now().plusMinutes(30).toDate());
                    String updateToken = Jwts.builder().setClaims(body).compact();
                    httpServletResponse.addHeader(AUTHORIZATION_HEADER, updateToken);

                    //添加用户凭证
                    PrincipalCollection principals = new SimplePrincipalCollection(body.getId(), JWTCost.UserNamePasswordRealm);//拼装shiro用户信息
                    WebSubject.Builder builder = new WebSubject.Builder(servletRequest, servletResponse);
                    builder.principals(principals);
                    builder.authenticated(true);
                    builder.sessionCreationEnabled(false);
                    WebSubject subject = builder.buildWebSubject();
                    //塞入容器,统一调用
                    ThreadContext.bind(subject);
                    filterChain.doFilter(httpServletRequest, httpServletResponse);
                }
            } else {
                httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
            }
        }
    }
}

登录失败处理

处理Shrio异常
@RestControllerAdvice
public class GlobalControllerExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public Object allExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
        String message = exception.getCause().getMessage();
        LogUtil.error(message);
        return new ResultInfo(exception.getClass().getName(), message);
    }

    /*=========== Shiro 异常拦截==============*/

    @ExceptionHandler(value = IncorrectCredentialsException.class)
    public String IncorrectCredentialsException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        return "IncorrectCredentialsException";
    }

    @ExceptionHandler(value = UnknownAccountException.class)
    public String UnknownAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        return "UnknownAccountException";
    }

    @ExceptionHandler(value = LockedAccountException.class)
    public String LockedAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        return "LockedAccountException";
    }

    @ExceptionHandler(value = ExcessiveAttemptsException.class)
    public String ExcessiveAttemptsException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        return "ExcessiveAttemptsException";
    }

    @ExceptionHandler(value = AuthenticationException.class)
    public String AuthenticationException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        return "AuthenticationException";
    }

    @ExceptionHandler(value = UnauthorizedException.class)
    public String UnauthorizedException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        return "UnauthorizedException";
    }
}
处理JWT异常 这是个坑,因为是在filter内发生的异常,@ExceptionHandler是截获不到的。
/**
 * 截获spring boot Error页面
 */
@RestController
public class GlobalExceptionHandler implements ErrorController {
    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping(value = "/error")
    public Object error(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 错误处理逻辑
        Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
        Throwable cause = exception.getCause();
        if (cause instanceof ExpiredJwtException) {
            response.setStatus(HttpStatus.GATEWAY_TIMEOUT.value());
            return new ResultInfo("ExpiredJwtException", cause.getMessage());
        }
        if (cause instanceof MalformedJwtException) {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            return new ResultInfo("MalformedJwtException", cause.getMessage());
        }
        return new ResultInfo(cause.getCause().getMessage(), cause.getMessage());
    }
}
关于权限等授权信息,可以直接放到Redis中实现缓存。我认为也是不错的。

源码奉上:githup-shiro分支 :温馨提示:平时测试代码可能比较乱。

如果更好的实现,让我学习,让我我进步,请联系我。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69300.html

相关文章

  • 使用JWT保护你的Spring Boot应用 - Spring Security实战

    摘要:创建应用有很多方法去创建项目,官方也推荐用在线项目创建工具可以方便选择你要用的组件,命令行工具当然也可以。对于开发人员最大的好处在于可以对应用进行自动配置。 使用JWT保护你的Spring Boot应用 - Spring Security实战 作者 freewolf 原创文章转载请标明出处 关键词 Spring Boot、OAuth 2.0、JWT、Spring Security、SS...

    wemall 评论0 收藏0
  • 高性能配置管理中心 duic

    摘要:配置中心在软件开发中随着业务的需要需求的变更程序的灵活我们时常需要在项目中设置各种开关或者配置项在往常时一般会采用配置文件的方式但是在这分布式集群时代采用传统的配置管理方式显得有点力不从心同时在我们的终端我们也时常需要各种配置在面对大量的终 ______ _ ______ |_ _ `. (_) . ___ | | | `...

    binaryTree 评论0 收藏0
  • 高性能配置管理中心 duic

    摘要:配置中心在软件开发中随着业务的需要需求的变更程序的灵活我们时常需要在项目中设置各种开关或者配置项在往常时一般会采用配置文件的方式但是在这分布式集群时代采用传统的配置管理方式显得有点力不从心同时在我们的终端我们也时常需要各种配置在面对大量的终 ______ _ ______ |_ _ `. (_) . ___ | | | `...

    newsning 评论0 收藏0
  • SpringBoot 入门简介

    摘要:这里使用的是数据库启动类上加上注解在启动类中添加对包扫描扫描多个包下的可以有以下几种方法扫描会自动加载相关配置,数据源就会自动注入到中,会自动注入到中,可以直接使用。有配置文件下的使用扫描多个包下的可以有以下几种方法扫描 Spring-Boot 学习笔记 1 Spring-Boot 介绍 1.1 什么是Spring-Boot Spring-Boot是由Pivotal团队提供的全新框架...

    chuyao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<