在先後端分離項目中使用SpringBoot集成Shiro

前言

       此次在處理一個小項目時用到了先後端分離,服務端使用springboot2.x。權限驗證使用了Shiro。先後端分離首先須要解決的是跨域問題,POST接口跨域時會預發送一個OPTIONS請求,瀏覽器收到響應後會繼續執行POST請求。 先後端分離後爲了保持會話狀態使用session持久化插件shiro-redis,持久化session能夠持久化到關係型數據庫,也能夠持久化到非關係型數據庫(主要是重寫SessionDao)。Shiro已提供了SessionDao接口和抽象類。若是項目中用到Swagger的話,還須要把swagger相關url放行。html

    

搭建依賴

<dependency>
    <!--session持久化插件-->
	<groupId>org.crazycake</groupId>
	<artifactId>shiro-redis</artifactId>
	<version>3.2.3</version>
</dependency>
<dependency>
    <!--spring shiro依賴-->
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.1</version>
</dependency>

 Shiro權限配置

一、ShiroConfig。這裏主要是shiro核心配置。好比SecurityManager、SessionManager、CacheManager。前端

public class ShiroConfig {

    @Value("${spring.redis.shiro.host}")
    private String host;
    @Value("${spring.redis.shiro.port}")
    private int port;
    @Value("${spring.redis.shiro.timeout}")
    private int timeout;
    @Value("${spring.redis.shiro.password}")
    private String password;


    /**
     * 權限規則配置
     **/
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("authc", new MyFormAuthorizationFilter());

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        //swagger資源不攔截
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**/**", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
        filterChainDefinitionMap.put("/configuration/security", "anon");
        filterChainDefinitionMap.put("/configuration/ui", "anon");

        filterChainDefinitionMap.put("/login/ajaxLogin", "anon");
        filterChainDefinitionMap.put("/login/unauth", "anon");
        filterChainDefinitionMap.put("/login/logout", "anon");
        filterChainDefinitionMap.put("/login/register","anon");
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setLoginUrl("/login/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    /**
     * shiro安全管理器(權限驗證核心配置)
     **/
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(cacheManager());

        return securityManager;
    }

    /**
     * 會話管理
     **/
    @Bean
    public SessionManager sessionManager() {
        MySessionManager sessionManager = new MySessionManager();
        sessionManager.setSessionIdUrlRewritingEnabled(false); //取消登錄跳轉URL後面的jsessionid參數
        sessionManager.setSessionDAO(sessionDAO());
        sessionManager.setGlobalSessionTimeout(-1);//不過時
        return sessionManager;
    }

    /**
     * 使用的是shiro-redis開源插件 緩存依賴
     **/
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host+":"+port);
        redisManager.setTimeout(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }

    /**
     * 使用的是shiro-redis開源插件 session持久化
     **/
    public RedisSessionDAO sessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }


    /**
     * 緩存管理
     **/
    @Bean
    public CacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }


    /**
     * 權限管理
     **/
    @Bean
    public MyShiroRealm myShiroRealm() {

        return new MyShiroRealm();
    }
}

 二、MyShiroRealm 用戶身份驗證、自定義權限。java

public class MyShiroRealm extends AuthorizingRealm {

    private Logger logger= LoggerFactory.getLogger(MyShiroRealm.class);

    @Resource
    UserDao userDao;


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("===================權限驗證==================");
        return null;
    }



    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
        User currentUser=userDao.findUser(token.getUsername());
        if(null == currentUser){
            throw new AuthenticationException("帳戶不存在");
        }

        if(!currentUser.getPassword().equals(new String(token.getPassword()))){
            throw new IncorrectCredentialsException("帳戶密碼不正確");
        }

        if(currentUser.getIsdel()==1){
            throw new LockedAccountException("帳戶已凍結");
        }

        Subject subject = SecurityUtils.getSubject();

        BIUser biUser=new BIUser();
        biUser.setUserId(currentUser.getUserId());
        biUser.setOrgId(currentUser.getOrgid());
        biUser.setUserName(currentUser.getUsername());
        biUser.setPassword(currentUser.getPassword());
        biUser.setSessionId(subject.getSession().getId().toString());
        biUser.setIsdel(currentUser.getIsdel());
        biUser.setCreateTime(currentUser.getCreatetime());

        logger.info("======已受權"+biUser.toString()+"====");

        return new SimpleAuthenticationInfo(biUser,biUser.getPassword(),biUser.getUserName());
    }
}

 三、MySessionManager。shiro權限驗證是根據客戶端Cookie中的JSESSIONID值來肯定身份是否合格。先後端分離後這個地方須要處理。客戶端調用服務端登錄接口,驗證經過後返回給客戶端一個token值(這裏我放的是sessionid)。客戶端保存token值,而後調用其餘接口時把token值放在header中。對前端來講也就是放在ajax的headers參數中。web

public class MySessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "Authorization";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public MySessionManager() {
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        //從前端ajax headers中獲取這個參數用來判斷受權
        String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (StringUtils.hasLength(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //從前端的cookie中取值
            return super.getSessionId(request, response);
        }

    }
}

 四、MyFormAuthorizationFilter。對於跨域的POST請求,瀏覽器發起POST請求前都會發送一個OPTIONS請求已肯定服務器是否可用,OPTIONS請求經過後繼續執行POST請求,而shiro自帶的權限驗證是沒法處理OPTIONS請求的,因此這裏須要重寫isAccessAllowed方法。ajax

public class MyFormAuthorizationFilter extends FormAuthenticationFilter {
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(servletRequest);
        if ("OPTIONS".equals(httpServletRequest.getMethod())) {
            return true;
        }
        return super.isAccessAllowed(servletRequest, servletResponse, o);
    }

}

五、處理跨域redis

@Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("PUT", "DELETE", "GET", "POST")
                .allowedHeaders("*")
                .exposedHeaders("access-control-allow-headers", "access-control-allow-methods", "access-control-allow" +
                        "-origin", "access-control-max-age", "X-Frame-Options","Authorization")
                .allowCredentials(false).maxAge(3600);

    }

 

    

原文出處:https://www.cnblogs.com/sword-successful/p/11093803.htmlspring

相關文章
相關標籤/搜索