shiro和redis集成,前后端分离

前言

框架:springboot+shiro+redis+vue
最近写前后端分离授权的对账平台系统,采取了shiro框架,若采用shiro默认的cookie进行授权验证时,一直存在由于跨域造成前端请求到的cookie每次都不相同,从而无法完成授权及验证的操作,即每次登陆成功时还是会显示未登陆。

Pom的引入

<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.1</version>
</dependency>

代码的编写

public class CustomRealm extends AuthorizingRealm {

    @Resource
    private AdminDao adminDao;

    @Resource
    private PermissionDao permissionDao;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //获取登录认证成功的主体
        Admin admin = (Admin)principals.getPrimaryPrincipal();
//        HashMap<String,String> admin = (HashMap)principals.getPrimaryPrincipal();
        //根据角色Id查询权限
        List<String> perms = new ArrayList<>();
//        if (!admin.get("roleId").equals("-1")){
//            perms = permissionDao.findPermsByRoles(admin.get("roleId"));
//        }
        if (!admin.getRoleId().equals("-1")){
            perms = permissionDao.findPermsByRoles(admin.getRoleId());
        }
        //对集合中的字符串做过滤处理,去除 空字符串及null
        perms = perms.stream().filter(s->s != null && !s.isEmpty()).collect(Collectors.toList());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(perms);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取通过subject提交的主体信息
        String userName = (String)token.getPrincipal();
        Admin admin = adminDao.queryByUserName(userName);
        if(admin == null||admin.getFlag()==0){
            throw new UnknownAccountException("账号不存在或账号不可用,请联系管理员");
        }
//        HashMap<String,String> map=new HashMap();
//        map.put("username",admin.getUsername());
//        map.put("roleId",admin.getRoleId());
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(admin,admin.getPassword(),ByteSource.Util.bytes(admin.getSalt()),super.getName());
        return info;
    }
}

shiroConfig

@Configuration
public class ShiroConfig {
    @Value("${redis.port}")
    private Integer port;

    @Value("${redis.host}")
    private String host;

    /**
     * @author  zhuyang
     * @description 创建域,包括认证管理器,安全管理器
     * @date 2021-03-07 15:55
     * @param credentialsMatcher:  MD5加密器
     * @return com.yxkj.web.accountchecking.realm.CustomRealm
     */
    @Bean
    public CustomRealm customRealm(HashedCredentialsMatcher credentialsMatcher){
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(credentialsMatcher);
        return customRealm;
    }
    /**
     * @author  zhuyang
     * @description 安全管理器  shiro里面所有的权限控制,认证都事先通过他,安全管理器是和过滤器工厂打交道的桥梁
     * @date 2021-03-07 15:54
     * @param customRealm:
     * @return org.apache.shiro.web.mgt.DefaultWebSecurityManager
     */
    //只把记住我和域注入到安全管理器
    //如果记住我注入到域中,那么安全管理器还要和域打交道,这样就绕了
    @Bean
    public DefaultWebSecurityManager securityManager(CustomRealm customRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm);
        //添加开始
        CarWashModularRealmAuthorizer authorizer = new CarWashModularRealmAuthorizer();
        List<Realm> list=new ArrayList<>();
        list.add(customRealm);
        authorizer.setRealms(list);
        securityManager.setAuthorizer(authorizer);
        //将自定义的会话管理器注入到安全管理器中
        securityManager.setSessionManager(sessionManger());
        //将自定义的redis缓存管理器注册到安全管理器中
        securityManager.setCacheManager(cacheManager());
        //添加结束
        return securityManager;
    }
   /**
    * @author  zhuyang
    * @date 2021-03-07 15:57
    * @param securityManager: 先经过过滤器工厂,再通过subject提交token给DefaultWebSecurityManager(安全管理器)
    * @return org.apache.shiro.spring.web.ShiroFilterFactoryBean
    */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
        //修改logout后的地址
        filtersMap.put("corsAuthenticationFilter", corsAuthenticationFilter());
        LogoutFilter logout = new LogoutFilter();
        logout.setRedirectUrl("/unauth");
        filtersMap.put("logout",logout);
        filterFactoryBean.setSecurityManager(securityManager);
        filterFactoryBean.setUnauthorizedUrl("/UnauthorizedUrl");
        Map map = new LinkedHashMap();
        map.put("/admin/getVerifyCode","anon");
        map.put("/admin/subLogin","anon");//访问登录
        map.put("/swagger-ui.html","anon");
        map.put("/admin/logout","logout");
        map.put("/doc.html","anon");
        map.put("/webjars/**","anon");
        map.put("/swagger-resources","anon");
        map.put("/freeQuery/manualCheck","anon");
        map.put("/v2/**","anon");
//        map.put("/**","anon");
        map.put("/**","authc");


        filterFactoryBean.setFilters(filtersMap);
        filterFactoryBean.setFilterChainDefinitionMap(map);
        filterFactoryBean.setLoginUrl("/unauth");
        return filterFactoryBean;
    }

    @Bean
    public HashedCredentialsMatcher credentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");
        credentialsMatcher.setHashIterations(1024);
        return credentialsMatcher;
    }


    /**
     * @author  zhuyang
     * @description  如果您在使用shiro注解配置的同时,引入了spring aop的starter,
     * 会有一个奇怪的问题,导致shiro注解的请求,不能被映射,需加入以下配置
     * @date 2021-03-07 16:14
     * @return org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        /**
         * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
         * 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,导致返回404。
         * 加入这项配置能解决这个bug
         */
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        return defaultAdvisorAutoProxyCreator;
    }

    /**
     * @author  zhuyang
     * @description 开启shiro注解的支持
     * @date 2021-03-07 16:20
     * @param securityManager:
     * @return org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
         AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
         advisor.setSecurityManager(securityManager);
         return advisor;
    }

    public CORSAuthenticationFilter corsAuthenticationFilter(){
        return new CORSAuthenticationFilter();
    }



//    @Bean
//    public SimpleCookie simpleCookie(){
//        SimpleCookie cookie = new SimpleCookie();
//        cookie.setName("rememberMe");
//        cookie.setMaxAge(18);
//        return cookie;
//    }




    /**
     * @author  zhuyang
     * @description 1.redis的控制器,操作redis
     * @date 2021-03-07 14:41
     */
    public RedisManager redisManager(){
        RedisManager redisManager=new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setTimeout(2000);
        return redisManager;
    }

    /**
     * @author  zhuyang
     * @description sessiionDao
     * @date 2021-03-06 17:31
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO sessionDAO=new RedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());
//        sessionDAO.setExpire(2000);
        return sessionDAO;
    }
    /**
     * @author  zhuyang
     * @description 会话管理器
     * @date 2021-03-06 17:30
     */
    public DefaultWebSessionManager sessionManger(){
        CustomSessionManager sessionManager=new CustomSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        sessionManager.setSessionIdCookieEnabled(false);
        sessionManager.setSessionIdUrlRewritingEnabled(false);
//        sessionManager.setTimeout(new DefaultSessionKey(),100);
        return sessionManager;
    }

    /**
     * @author  zhuyang
     * @description  缓存管理器
     * @date 2021-03-06 17:31
     */
    public RedisCacheManager cacheManager(){
        RedisCacheManager redisCacheManager=new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }
}

SessionId的获取

public class CustomSessionManager extends DefaultWebSessionManager {

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader("token");
        //如果请求头中有 token 则其值为sessionId
        if (!StringUtils.isEmpty(id)) {
            //sessionId存放的位置
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
            //sesssionId的值
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            //是否验证
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }

}

解决403的问题

public class CORSAuthenticationFilter extends FormAuthenticationFilter {
    public CORSAuthenticationFilter() {
        super();
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if(request instanceof HttpServletRequest){
            if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")){
                System.out.println("OPTIONS请求");
                return true;
            }
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }

}

授予管理员的一切权限,且不可修改

public class CarWashModularRealmAuthorizer extends ModularRealmAuthorizer {
    @Override
    public  boolean isPermitted(PrincipalCollection principals, String permission){
//        HashMap user = (HashMap) principals.getPrimaryPrincipal();
        Admin user = (Admin) principals.getPrimaryPrincipal();
        // 如果是管理员拥有所有的访问权限
        return user.getUsername().equals("gly") || super.isPermitted(principals, permission);
    }
    @Override
    public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
        Admin user = (Admin) principals.getPrimaryPrincipal();
        // 如果是管理员拥有所有的角色权限
        return user.getUsername().equals("gly") || super.hasRole(principals, roleIdentifier);
    }
}

异常拦截

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(AuthorizationException.class)
    @ResponseBody
    public Map handleAuthorizationException(AuthorizationException e){
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", "1000002");
        map.put("msg", "该用户没有对应的权限");
        return map;
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result exceptionHandler(Exception e){
        e.printStackTrace();
        log.error("异常日志打印"+e.getMessage());
        System.out.println(e.getClass().getName());
        return new Result(500,e.getMessage());
    }
}

其他处理

@Controller
@ApiIgnore
public class ShiroController {
    @RequestMapping(value = "/unauth")
    @ResponseBody
    public Object unauth() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", "1000000");
        map.put("msg", "未登录");
        return map;
    }

    @RequestMapping(value = "/UnauthorizedUrl")
    @ResponseBody
    public Object UnauthorizedUrl() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", "1000001");
        map.put("msg", "未授权");
        return map;
    }
}
XFS
原文地址:https://www.cnblogs.com/xiaofengshan/p/14586327.html