Shiro

学习Shiro

Imagem de capa

官方文档地址

demo地址

版本说明

基本框架

说明

其他的东西我就不说了,网上一大堆.
这个例子主要针对 api 保护,后台只提供api,不提供页面访问
这个例子中没有指定登录,你需要自己写登录,就按照你自己的业务写即可,
只要token能查询到就可以,如果需要可以在配置上加入login
保证这些代码可以100%运行,只有一些service需要根据你自己的业务逻辑定义
这里就不给出了
顺便把一些坑给填了

创建项目

用idea创建一个spring boot 项目,这里就不多说了

shiro maven

       <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.4.0-RC2</version>
        </dependency>

创建类

1.OAuth2Token

package com.web.authorizing;


import org.apache.shiro.authc.AuthenticationToken;

public class OAuth2Token implements AuthenticationToken {
    
    private static final long serialVersionUID = -1586550416883518120L;
    private String token;

    public OAuth2Token(String token) {
        this.token = token;
    }

    @Override
    public String getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

2.UserRealm

package com.web.authorizing;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Set;

/**
 * 认证身份
 */
@Component
public class UserRealm extends AuthorizingRealm {

    /**
     * 这里导入自己的service
     * 需要 用户的service token的service 权限的service 或者角色的service
     */
    @Autowired
    private UserBiz userBiz;
    @Autowired
    private TokenBiz tokenBiz;
    /**
     * 菜单的service 这里是权限映射菜单
     */
    @Autowired
    private MenuBiz menuBiz;

   /**
    * 判断是否能处理AuthenticationToken
    */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof OAuth2Token;
    }

   /**
    * 初始化
    */
    @Autowired
    public UserRealm(CacheManager cacheManager) {
        //配置缓存
        super(cacheManager);
        //开启身份验证缓存
        this.setAuthenticationCachingEnabled(true);
        //开启授权缓存
        this.setAuthorizationCachingEnabled(true);

    }

    /**
     * 身份验证
     * 用来身份验证的
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取请求到的token,这个token在AuthenticatingFilter中封装的
        String token = authenticationToken.getPrincipal().toString();
        if (token == null) {
            return null;
        }
        //通过token获取用户信息,下面根据自己的业务进行处理
        SysUserToken userToken = tokenBiz.queryByToken(token);
        //不合格抛出异常
        if (userToken == null || userToken.getMobile() && userToken.getExpireTime().getTime() < System.currentTimeMillis()) {
            throw new IncorrectCredentialsException("r.error.token.failure");
        } else {
            tokenBiz.updateExampleTiemByUserId(userToken.getUserId(), getExampleTime());
        }
        //判断用户是否锁定
        SysUser user = userBiz.queryUserStatusAndUserIdByUserId(userToken.getUserId());
        if (user.getStatus() == 0) {
            throw new LockedAccountException("r.error.user.lock");
        }
        //返回SimpleAuthenticationInfo(用户对象,token(验证的凭证),getName())
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(token, token, getName());
         SecurityUtils.getSubject().getSession().setAttribute(token,user);
        return authenticationInfo;
    }

    /**
     * 授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取用户对象
        SysUser sysUser = (SysUser) principalCollection.getPrimaryPrincipal();
        //在数据库查询权限
        Set<String> permsSet = menuBiz.queryPermsByDelFlageAndUserId(sysUser.getUserId());
        if (permsSet.size() == 0) {
            return null;
        }
        //配置权限,也可以配置角色simpleAuthorizationInfo.setRoles()
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permsSet);
        return simpleAuthorizationInfo;
    }



   //这个可以不用管,这是自己的业务需要
    private Date getExampleTime() {
        return new Date(System.currentTimeMillis() + UserRealm.EXAMPLE_TIME);
    }
}

3.OAuth2Filter

package com.web.authorizing;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.web.util.R;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * oauth2过滤器
 */
@Slf4j
public class OAuth2Filter extends AuthenticatingFilter {

    //使用的jackson 
    private ObjectMapper om;

    //这个可以不用管,用来做国际化的
    private MessageSource messageSource;

    //可以把这个构造方法删除
    public OAuth2Filter(ObjectMapper om, MessageSource messageSource) {
        this.om = om;
        this.messageSource = messageSource;
    }

    /**
     * 创建token
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求中token
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return new OAuth2Token(token);
    }
   
   /**
    *表示是否允许访问
    */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }

    /**
     * 表示当访问拒绝时是否已经处理了
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //获取token
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            AuthenticationException exception =  new AuthenticationException("r.error.token.null");
            onLoginFailure(null,exception,request,response);
            return false;
        }
        return executeLogin(request, response);
    }


    /**
     * 登录失败则调用
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        try {
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            var locale = LocaleContextHolder.getLocale();
            String message = messageSource.getMessage(throwable.getMessage(), null, locale);
            //这个R对象可以自定义,我这里只是继承了HaseMap
            R r = R.error(HttpStatus.UNAUTHORIZED.value(), message);
            httpResponse.getWriter().print(om.writeValueAsString(r));
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        return false;
    }

    /**
     * 获取请求的token
     * 从header中获取token
     * 如果header中不存在token,则从参数中获取token
     */
    private String getRequestToken(HttpServletRequest httpRequest) {
        String token = httpRequest.getHeader("token");
        if (StringUtils.isBlank(token)) {
            token = httpRequest.getParameter("token");
        }
        return token;
    }

}

5.ShiroConfig

package com.web.config;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.llvision.web.authorizing.OAuth2Filter;
import com.llvision.web.authorizing.UserRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.Map;

@Configuration
public class ShiroConfig {


    /**
     * session 管理
     *
     * @return
     */
    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

    /**
     * 授权管理
     *
     * @param userRealm
     * @param sessionManager
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager,CacheManager cacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        securityManager.setSessionManager(sessionManager);
        //这一步比较重要,不然不能使用SecurityUtils相关的静态方法
        SecurityUtils.setSecurityManager(securityManager);
        return securityManager;
    }

  /**
   * shiro 自带缓存
   */
    @Bean
    protected CacheManager cacheManager(){
        return new MemoryConstrainedCacheManager();
    }

   /**
    * shiro的Filter 这里只要是空的就行,和ShiroFilterFactoryBean选择一个就行
    * 这个不能添加Filter
    */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition(){
        return new DefaultShiroFilterChainDefinition();
    }

    /**
     * shiro工厂bean
     * filterMap
     * anon:所有url都都可以匿名访问
     * authc: 需要认证才能进行访问
     * user:配置记住我或认证通过可以访问
     *
     * @param securityManager
     * @return
     */
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, ObjectMapper om, MessageSource messageSource) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        OAuth2Filter auth2Filter = new OAuth2Filter(om, messageSource);

        shiroFilter.setSecurityManager(securityManager);
        Map<String, Filter> filters = Maps.newHashMap();
        filters.put("oauth2", auth2Filter);
        shiroFilter.setFilters(filters);
        Map<String, String> filterMap = Maps.newLinkedHashMap();
        //需要能够访问登录接口
        filterMap.put("/login","anon")
        filterMap.put("/**", "perms");
        //你自己的拦截器一定要放在最后,这个filter还不能交给spring管理
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }


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

    /**
     * 配置代理
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

}

6.

package com.web.api;

import com.llvision.web.entity.SysUser;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    /**
     * 登录接口,这里的业务逻辑按照自己的业务逻辑写就行
     * 但是token要能查询到
     */
    @PostMapping("login")
    public String login(@RequestBody SysUser user) {
        return "token";

    }

    /**
     * 这里可以直接使用shiro相关的注解
     * 这个使用的事权限注解,更多注解需要自己查询
     */
    @RequiresPermissions("userInfo:add")
    @PostMapping("test")
    public String test() {
         Session subject = SecurityUtils.getSubject().getSession();
        SysUser user = (SysUser) subject.getAttribute(SecurityUtils.getSubject().getPrincipal().toString());
        return "ok";
    }

   /**
     * 退出登录
     */
    @PostMapping("logout")
    public String test() {
        //添加自己的业务逻辑
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "ok";
    }
}