1. 程式人生 > 實用技巧 >【單點登入】SpringBoot、Shiro、Redis實現SessionId共享

【單點登入】SpringBoot、Shiro、Redis實現SessionId共享

1.新建 CustomRealm類 重新doGetAuthenticationInfo方法

package com.alpha.shiro.realm;

import com.alpha.model.system.User;
import com.alpha.service.system.PermissionService;
import com.alpha.service.system.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 描述:
 *
 * @author caojing
 * @create 2019-01-27-13:57
 */
public class CustomRealm extends AuthorizingRealm {


    @Autowired
    UserService userService;

    @Autowired
    PermissionService permissionService;



















    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        List<String> allPermission = permissionService.getPermissionImp(username);
        Set<String> stringSet = new HashSet<>(allPermission);
        info.setStringPermissions(stringSet);

        return info;
    }

    /**
     * 這裡可以注入userService,為了方便演示,我就寫死了帳號了密碼
     * private UserService userService;
     * <p>
     * 獲取即將需要認證的資訊
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("-------身份認證方法--------");
        String userName = (String) authenticationToken.getPrincipal();
        String userPwd = new String((char[]) authenticationToken.getCredentials());
        //根據使用者名稱從資料庫獲取密碼
        User u = userService.login(userPwd,userName);
        if (u == null) {
            throw new AccountException("賬號或密碼錯誤");
        }
        return new SimpleAuthenticationInfo(userName, userPwd,getName());
    }
}

  2.新建CustomSessionManager 重寫getSessionId方法

package com.alpha.shiro.session;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/**
 * @version:     1.0
 * @since:       JDK 1.8.0_91
 * @Description: 適用於前後端分離情況下對sessionId的獲取
 *
 * <br>Modification History:<br>

 * Date       |      Author      |      Version    |       Description<br>
 * ------------------------------------------------------------------<br>

 * 2018年10月23日   |     yao_x_x      |         1.0        |         1.0 Version

 */

public class CustomSessionManager extends DefaultWebSessionManager {

    /**
     * 獲取請求頭中key為“Authorization”的value == sessionId
     */
    private static final String AUTHORIZATION ="Authorization";

    private static final String REFERENCED_SESSION_ID_SOURCE = "cookie";

    /**
     *  @Description shiro框架 自定義session獲取方式<br/>
     *  可自定義session獲取規則。這裡採用ajax請求頭 {@link }攜帶sessionId的方式
     */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        // TODO Auto-generated method stub
        String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (StringUtils.isNotEmpty(sessionId)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return sessionId;
        }
        return super.getSessionId(request, response);
    }

}

  3.新建ShiroConfig 類,重點實現SessionDAO

package com.alpha.shiro.config;


import com.alpha.redis.sso.dao.RedisSessionDao;
import com.alpha.shiro.realm.CustomRealm;
import com.alpha.shiro.session.CustomSessionManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 描述:
 *
 * @author caojing
 * @create 2019-01-27-13:38
 */
@Configuration
public class ShiroConfig {

    @Value("${spring.system.login-url}")
    private String loginUrl;



    /**
     * Shiro生命週期處理器
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 開啟Shiro的註解(如@RequiresRoles,@RequiresPermissions),需藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    /**
     * 開啟aop註解支援
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    @Bean("securityManager")
    public org.apache.shiro.mgt.SecurityManager securityManager(@Qualifier("sessionManager")SessionManager sessionManager) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(customRealm());
        manager.setSessionManager(sessionManager);
        return manager;
    }

    @Bean
    public RedisSessionDao getRedisSessionDao(){
        return new RedisSessionDao();
    }

    @Bean("sessionManager")
    public SessionManager sessionManager(){
        CustomSessionManager manager = new CustomSessionManager();
		/*使用了shiro自帶快取,
		如果設定 redis為快取需要重寫CacheManager(其中需要重寫Cache)
		*/
        manager.setSessionDAO(getRedisSessionDao());
        return manager;
    }



    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl(loginUrl);
        shiroFilterFactoryBean.setUnauthorizedUrl(loginUrl);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // <!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
        filterChainDefinitionMap.put("/static/**", "anon");

        filterChainDefinitionMap.put("/admin/user/**", "anon");
        filterChainDefinitionMap.put("/unAuth", "anon");
        filterChainDefinitionMap.put("/login.html", "anon");
        filterChainDefinitionMap.put("/admin/home/login", "anon");
        filterChainDefinitionMap.put("/admin/website/getInfo", "anon");



        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("/unAuth.html", "anon");

//        filterChainDefinitionMap.put("/user/**", "authc");
        //主要這行程式碼必須放在所有許可權設定的最後,不然會導致所有 url 都被攔截 剩餘的都需要認證
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;

    }




    @Bean
    public CustomRealm customRealm() {
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }
}

  4,實現SessionDAO 新建RedisSessionDao

package com.alpha.redis.sso.dao;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.TimeUnit;

/**
 * Created by Administrator on 2018/7/31.
 */
@Service
public class RedisSessionDao extends AbstractSessionDAO {
    // Session超時時間,單位為毫秒
    private long expireTime = 120000;

    @Autowired
    private RedisTemplate redisTemplate;// Redis操作類,對這個使用不熟悉的,可以參考前面的部落格

    public RedisSessionDao() {
        super();
    }

    public RedisSessionDao(long expireTime, RedisTemplate redisTemplate) {
        super();
        this.expireTime = expireTime;
        this.redisTemplate = redisTemplate;
    }

    @Override // 更新session
    public void update(Session session) throws UnknownSessionException {
        System.out.println("===============update================");
        if (session == null || session.getId() == null) {
            return;
        }
        session.setTimeout(expireTime);
        redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
    }

    @Override // 刪除session
    public void delete(Session session) {
        System.out.println("===============delete================");
        if (null == session) {
            return;
        }
        redisTemplate.opsForValue().getOperations().delete(session.getId());
    }

    @Override
// 獲取活躍的session,可以用來統計線上人數,如果要實現這個功能,可以在將session加入redis時指定一個session字首,統計的時候則使用keys("session-prefix*")的方式來模糊查詢redis中所有的session集合
    public Collection<Session> getActiveSessions() {
        System.out.println("==============getActiveSessions=================");
        return redisTemplate.keys("*");
    }

    @Override// 加入session
    protected Serializable doCreate(Session session) {
        System.out.println("===============doCreate================");
        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, sessionId);

        redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
        return sessionId;
    }

    @Override// 讀取session
    protected Session doReadSession(Serializable sessionId) {
        System.out.println("==============doReadSession=================");
        if (sessionId == null) {
            return null;
        }
        return (Session) redisTemplate.opsForValue().get(sessionId);
    }

    public long getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(long expireTime) {
        this.expireTime = expireTime;
    }

    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;

    }
}

  5.在yml上新增配置

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    password:

  完成!