springboot+jwt+shiro

shiro简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

引用至百度百科

最近弄一个新的服务,比较简单,整合了一下安全框架

springboot+jwt+shiro还是比较常见的安全框架整合,简单记录一下这次整合过程

首先,pom.xml 文件添加依赖

 <!--整合Shiro安全框架-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>${shiro.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>net.sf.ehcache</groupId>
                    <artifactId>ehcache</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>
        <!--集成jwt实现token认证-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>

配置Shiro

@Configuration
public class ShiroConfiguration {
    /**
     * ehcache缓存方案
     *
     * @return
     */
    @Bean
    public CacheManager shiroCacheManager() {
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
        return cacheManager;
    }

    @Bean
    public JWTTokenRealm jwtTokenRealm() {
        JWTTokenRealm jwtTokenRealm = new JWTTokenRealm();
//        jwtTokenRealm.setCredentialsMatcher(credentialsMatcher());
        jwtTokenRealm.setCacheManager(shiroCacheManager());
        return jwtTokenRealm;
    }



    @Bean
    public Authenticator authenticator() {
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        List<Realm> realms = new ArrayList<>();
        realms.add(jwtTokenRealm());
        authenticator.setRealms(realms);
        return authenticator;
    }

    /**
     * 安全管理配置
     *
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(jwtTokenRealm());
        securityManager.setCacheManager(shiroCacheManager());
//        securityManager.setSessionManager(getSessionManage());
        // 关闭shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        SecurityUtils.setSecurityManager(securityManager);
        return securityManager;
    }

    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
                new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }



    /**
     * 配置shiro的拦截器链工厂,默认会拦截所有请求
     *
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        // 配置安全管理
        filterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filterMap = new LinkedHashMap<>();
//        filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());
        filterMap.put("jwt", new JWTFilter());
//        filterMap.put("kickout", kickoutSessionControlFilter());
        filterFactoryBean.setFilters(filterMap);

        // 配置拦截地址和拦截器
        // 必须使用LinkedHashMap,因为拦截有先后顺序
        // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 登录接口不设置权限认证
        filterChainDefinitionMap.put("/login", "anon");
        //swagger2免拦截
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");

        // 登出不需要认证
        filterChainDefinitionMap.put("/logout", "anon");

        // 不需要认证的接口配置
        filterChainDefinitionMap.put("/app/**", "anon");


        // 剩下的其他资源地址全部需要用户认证后才能访问
        filterChainDefinitionMap.put("/**", "jwt");
        filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return filterFactoryBean;
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }


}

配置文件

ehcache-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shirocache">
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
            maxElementsInMemory="10000"
            maxElementsOnDisk="0"
            eternal="true"
            overflowToDisk="true"
            diskPersistent="false"
            timeToIdleSeconds="0"
            timeToLiveSeconds="0"
            diskSpoolBufferSizeMB="50"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LFU"
    />

    <!-- 登录记录缓存 锁定10分钟 -->
    <cache name="passwordRetryCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="authorizationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="authenticationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="shiro-activeSessionCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="shiro-kickout-session"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

</ehcache>

yml文件配置

#token过期时间 (3 * 60 * 60 * 1000)
tokenExpireTime: 10800000

jwt集成相关类

/**
 * jwt过滤器 实现token鉴权
 */
public class JWTFilter extends BasicHttpAuthenticationFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class);

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // 获取Authorization字段
        String token = httpServletRequest.getHeader("Authorization");
        if (token != null) {
            String username = JWTUtil.getUsername(token);
            if (username == null) {
                responseOnAccessDenied(request, response);
                return false;
            }
            JWTToken jwtToken = new JWTToken(username, token);
            try {
                getSubject(request, response).login(jwtToken);
            } catch (Exception e) {
                responseOnAccessDenied(request, response);
                return false;
            }
            return true;
        }
        responseOnAccessDenied(request, response);
        return false;
    }

    private void responseOnAccessDenied(ServletRequest request, ServletResponse response) {
        LOGGER.warn("未经授权访问[{}]被拦截.", ((HttpServletRequest) request).getRequestURL());
        String responseJson = "{\"code\":401,\"msg\":\"Unauthorized.\"}";

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json");
        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());

        try (PrintWriter out = response.getWriter()) {
            out.print(responseJson);
        } catch (IOException e) {
            LOGGER.error("获取PrintWriter输出流失败,失败原因", e);
        }
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers",
                httpServletRequest.getHeader("Access-Control-Request-Headers"));
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    @Bean
    public FilterRegistrationBean registration(JWTFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean(filter);
        registration.setEnabled(false);
        return registration;
    }
}

 

public class JWTToken implements AuthenticationToken {

    /**
     * 用户名
     */
    private String username;

    /**
     * 令牌
     */
    private String token;

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

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

    @Override
    public Object getCredentials() {
        return token;
    }
}
/**
 * 基于jWT自定义的Realm
 *
 */
public class JWTTokenRealm extends AuthorizingRealm {

    private static final Logger LOGGER = LoggerFactory.getLogger(JWTTokenRealm.class);

    @Resource
    private AdminService adminService;

    /**
     * Convenience implementation that returns
     * <tt>getAuthenticationTokenClass().isAssignableFrom( token.getClass() );</tt>.  Can be overridden
     * by subclasses for more complex token checking.
     * <p>Most configurations will only need to set a different class via
     * {@link #setAuthenticationTokenClass}, as opposed to overriding this method.
     *
     * @param token the token being submitted for authentication.
     * @return true if this authentication realm can process the submitted token instance of the class, false otherwise.
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }


    /**
     * 认证信息(身份验证,用于用户登录)
     *
     * @param authenticationToken 用来验证用户身份
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();

        AdminEntity adminEntity = adminService.query().eq("user_name", username).one();
        // 设置部分参数为null
//        tdUser.setPartner(null);
//        adminEntity.setTdRoleList(null);

        String token = (String) authenticationToken.getCredentials();
        // 校验token
        checkToken(token, username, adminEntity.getPassword());
        return new SimpleAuthenticationInfo(adminEntity, token, getName());
    }

    /**
     * 校验token
     *
     * @param token    令牌
     * @param username 用户名
     * @param password 加密后的密码
     */
    private void checkToken(String token, String username, String password) {
        if (!JWTUtil.verify(token, username, password)) {
            throw new AcException();
        }
    }

    /**
     * 更新权限等信息时清除缓存
     */
    public void clearCached() {
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principals);
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}

登录接口方法内进行调用,返回token给前端

String token = JWTUtil.sign(adminEntity.getUserName(), adminEntity.getPassword());

之后其他没有免拦截的接口请求时,在请求头加入token即可

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值