shiro使用(最终集成springboot代码合集)

至此,shiro和springboot和JWT的集成全部完毕,下面为完整代码。

第一:shiroConfig,整个shiro的配置类。

这里sesssion交给了shiro默认去管理,不在做过多的配置,一方面shiro的session时间够用了,另一方便,没必要把配置类搞得这么庞大,以前的shiro文章里特地讲了shiro的session,包括集成quatz。请看这里


/**
 * shiro配置类
 */
@Configuration
public class ShiroConfig {

    @Bean("rememberCookie")
    public SimpleCookie rememberCookie(){
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setHttpOnly(true);
        simpleCookie.setName("remeberCookie");
        simpleCookie.setMaxAge(360000);
        return simpleCookie;
    }
    @Bean("cookieRememberMe")
    public CookieRememberMeManager cookieRememberMe(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberCookie());
        return cookieRememberMeManager;
    }

    /**
     * 密码加密
     * @return
     */
    @Bean("credentialsMatcher")
    public HashedCredentialsMatcher credentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("SHA-256");
        hashedCredentialsMatcher.setHashIterations(20);
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }
    @Bean("shiroRealm")
    public ShiroRealm shiroRealm(){
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(credentialsMatcher());
        return shiroRealm;
    }

    /**
     * 这里session交给shiro默认管理,不去做详细配置
     * @return
     */
    @Bean("securityManager")
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //传入自定义shiroRealm
        securityManager.setRealm(shiroRealm());
        //这里要注意,setRememberMeManager里传入的类型是CookieRememberMeManager,不要搞错了
        securityManager.setRememberMeManager(cookieRememberMe());
        return securityManager;
    }

    /**
     * 这里不设置loginUrl,传统的前后端不分离是可以配置,
     * 因为这里前后端分离,前端没有获取到token自然会路由到登录页面进行登录操作,不再需要通过loginUrl定位到视图view,SpringBoot只负责后端
     * 同时注意这里的filterMap中对于拦截路径的配置要以 / 开头,否则找不到对应的Controller,
     * 切记:一定要把/** = authc放到最后!!!!!
     * 2019-05-12补充说明:对于loginUrl最终还是需要的,filter里会进行是否为登录url的判断。
     * @param securityManager
     * @return
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager,AuthFilter authFilter){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/user/login");
        //设置自定义filter
        Map filter = new HashMap<>();
        filter.put("auth",authFilter);
        shiroFilterFactoryBean.setFilters(filter);
        //同时这里注意,使用LinkedHashMap来保证拦截器的顺序性
        Map filterMap = new LinkedHashMap<>();
        //登录逻辑这里不用anno,统一走自定义filter
        //filterMap.put("/user/login","anon");
        filterMap.put("/user/insert","auth");
        filterMap.put("/**","auth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * Spring管理Shiro生命周期
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
}

第二:shiroRealm,承担着shiro的认证和授权工作。

/**
 * 自定义shiroRealm
 */
public class ShiroRealm extends AuthorizingRealm {
    @Autowired
    UserService service;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        User user = 对应数据库查询操作,可根据自己的代码书写即可;
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getSalt()),getName());
        return simpleAuthenticationInfo;
    }
}

第三:AuthFilter.shiro所有请求必须要走的过滤器


/**
 * shiro过滤器
 */
@Component("authFilter")
public class AuthFilter extends FormAuthenticationFilter {

    @Autowired
    JwtUtil jwtUtil;

    /**
     * 判断token是否为空、过期
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            String token = getRequestToken((HttpServletRequest) request);
            if (ObjectUtils.isNull(token)){
                return false;
            }
            if (StringUtils.isBlank(token)) {
                throw new CustomException(jwtUtil.getHeader()+"不能为空", HttpStatus.SC_UNAUTHORIZED);
            }
            Claims claims = jwtUtil.parseToken(token);
            if (ObjectUtils.isNull(claims) || jwtUtil.isTokenExpired(claims.getExpiration())) {
                throw new CustomException(jwtUtil.getHeader()+"token过期",HttpStatus.SC_UNAUTHORIZED);
            }
        return true;
    }

    /**
     * 上面的方法如果返回false,则接下来会执行这个方法,如果返回为true,则不会执行这个方法
     * 判断是否为登录url,进一步判断请求是不是post
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取请求中的token,首先从请求头中获取,如果没有,则尝试从请求参数中获取
     *
     * @param request
     * @return
     */
    private String getRequestToken(HttpServletRequest request) {
        String token = request.getHeader(jwtUtil.getHeader());
        if (StringUtils.isBlank(token)) {
            token = request.getParameter(jwtUtil.getHeader());
        }
        return token;
    }
}

第四:JwtUtil.负责JWT的创建和校验。

/**
 * JWT token 工具类,提供JWT生成,校验,工作
 */
@ConfigurationProperties(prefix = "dhb.jwt")
@Component
public class JwtUtil {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private String secret;
    private Long expire;
    private String header;


    /**
     *
     * 生成JWT token
     * @param userId
     * @return
     */
    public String generateToken(Long userId) {
        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(userId + "")
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();

    }

    /**
     *
     * 解析JWT token
     * @param token
     * @return
     */
    public Claims parseToken(String token) {
        try {

            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            logger.info("解析token出错");
            return null;
        }
    }

    /**
     *
     * 校验token是否过期
     * @param expiprationTime
     * @return
     */
    public boolean isTokenExpired(Date expiprationTime){
        return expiprationTime.before(new Date());
    }
    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public Long getExpire() {
        return expire;
    }

    public void setExpire(Long expire) {
        this.expire = expire;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }
}

写在最后:虽然看起来这有这四大步,但是回头看看自己花了好长时间才走到现在这样。从一开始的认识shiro的架构,简单的基本使用,到实际运用中踩的坑,以及为什么要用token,不用session,以及舍弃配置复杂的session和quartz集成管理。这期间,每一步自己都在想,我为什么要用某一个技术,在实际编码中如何做到不让最后集成起来很臃肿。

推荐对于平时自己测试接口的时候,使用PostMan或者Yapi,千万不要为了测一个小小的接口又去写页面之类的,这样会使你脱离工作重心,最后啥啥都没干好。

还是那句话:既然要做,就做的细致一点,对得起自己!

你可能感兴趣的:(shiro)