springboot使用shiro和pac4j-cas 实现cas单点登录

前言:

之前项目使用springmvc开发的cas client,由于以后新项目需要改用springboot开发,所以需要使用springboot来实现cas的单点登录、并完成对自定义需求的实现;之前使用shiro-cas,官方在1.3版本已经标注了过时,根据推荐使用了pac4j-cas来实现;

文章目录

一、集成shiro-spring也pac4j-cas,完成基本的cas单点登录功能;

二、添加单点登出支持,

          默认的shiroSessionStore中getTrackableSession返回直接返回null,导致收到cas服务端发过来的单点登出请求后不会清除对应st创建的session,无法登出;所以自定义CustomShiroSessionStore,实现了默认为null的方法,重启后可以成功登出;

@Override
	public Object getTrackableSession(J2EContext context) {
		return getSession(true);
	}

	@Override
	public SessionStore<J2EContext> buildFromTrackableSession(
			J2EContext context, Object trackableSession) {
		  if(trackableSession != null) {
	            return new ShiroProvidedSessionStore((Session) trackableSession);
	        }
	        return null;
	}

 

三、自定义身份验证过滤器CustomCasFilter,重写anon拦截功能,实现某些页面允许未登录访问,但是当cookie中包含tgc时(在其他项目中已经登录),可以自动登录;

根据需求,有一下页面可以允许未登录访问,但是当用户在其他项目中已经登录的后,打开这些页面也需要能够显示用户信息。因此shiro默认的 anon过滤器就无法满足该需求;这边自定义了一个CustomCasFilter,在doFilter中检查cookie中是否包含了tgc,如果已经包含了,就去做登录流程;否则不处理( 这边能获取到tgc是因为已经把cas服务端写cookie的域改成了一级域名,而该项目的域名与cas服务端同属于同一一级域名)

 @Override
	    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {

	        assertNotNull("securityLogic", securityLogic);
	        assertNotNull("config", config);

	        final HttpServletRequest request = (HttpServletRequest) servletRequest;
	        final HttpServletResponse response = (HttpServletResponse) servletResponse;
	        final SessionStore<J2EContext> sessionStore = config.getSessionStore();
	        final J2EContext context = new J2EContext(request, response, sessionStore != null ? sessionStore : ShiroSessionStore.INSTANCE);
	        if(!SecurityUtils.getSubject().isAuthenticated()){
	        	Collection<Cookie> cookies = context.getRequestCookies();
		        Optional<Cookie> fid = cookies.stream().filter(cookie -> "fid".equals(cookie.getName())).findFirst();  //我们tgc存储的name就是fid
                    
		        if(fid.isPresent()&& !StringUtils.isEmpty(fid.get().getValue())) {
		        	//在其他项目中已经登录、跳去登录验证;
		        	 securityLogic.perform(context, config, (ctx, profiles, parameters) -> {

		 	            filterChain.doFilter(request, response);
		 	            return null;

		 	        }, J2ENopHttpActionAdapter.INSTANCE, clients, authorizers, matchers, multiProfile);
		        }
	        }
	        // 不登录也能访问的页面
	        filterChain.doFilter(request, response);
	    }

 

四、添加CustomContextThreadLocalFilter,把Pac4jPrincipal身份信息存入threadlocal,避免每次需要先判断subject.getPrincipals();是否为空;

因为每次使用用户信息都需要2次npe判断,所以使用CustomContextThreadLocalFilter过滤器来减少重复代码

@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
			FilterChain filterChain) throws IOException, ServletException {
		try {
			Subject subject = SecurityUtils.getSubject();
			PrincipalCollection pcs = subject.getPrincipals();
			if(null !=pcs){
				Pac4jPrincipal p = pcs.oneByType(Pac4jPrincipal.class);
				ContextHolder.setPac4jPrincipal(p);
			}
			filterChain.doFilter(servletRequest, servletResponse);
		} finally {
			ContextHolder.clear();
		}
		
	}
package com.spean.shiro_cas.util;

import io.buji.pac4j.subject.Pac4jPrincipal;

/**
 * 线程内提供  Pac4jPrincipal 访问
 * @author ssss
 *
 */
public class ContextHolder {

	private static final ThreadLocal<Pac4jPrincipal> threadLocal = new ThreadLocal<Pac4jPrincipal>();
	
	public static void setPac4jPrincipal(final Pac4jPrincipal pac4jPrincipal) {
		threadLocal.set(pac4jPrincipal);
	}
	
	public static Pac4jPrincipal getPac4jPrincipal() {
		return threadLocal.get();
	}
	
	public static void clear() {
        threadLocal.set(null);
    }
}

五、添加支持登出后跳转到指定地址

手动登录出后需要指定跳转地址、默认接收的指定参数名为"url",正则校验位 /.*;这边修改正则校验,扩大url的范围

  // 注销 拦截器
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setConfig(config);
        logoutFilter.setCentralLogout(true);
        logoutFilter.setLocalLogout(true);
        //添加logout后  跳转到指定url  url的匹配规则  默认为 /.*;  
        logoutFilter.setLogoutUrlPattern(".*");
        logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName);
        filters.put("logout",logoutFilter);

 

 

 

完整代码:

引入依赖

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
 		<dependency>		
            <groupId>io.buji</groupId>
            <artifactId>buji-pac4j</artifactId>
            <version>${buji.version}</version>
        </dependency>
        <dependency>
            <groupId>org.pac4j</groupId>
            <artifactId>pac4j-cas</artifactId>
            <version>${pac4j.version}</version>
        </dependency>

创建Pac4jConfig

package com.spean.shiro_cas.config.shiro;

import org.pac4j.cas.client.CasClient;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.cas.config.CasProtocol;
import org.pac4j.cas.logout.DefaultCasLogoutHandler;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.WebContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Pac4jConfig {

	 /** 地址为:cas地址 */
    @Value("${cas.server.url}")
    private String casServerUrl;

    /** 地址为:验证返回后的项目地址:http://localhost:8081 */
    @Value("${cas.project.url}")
    private String projectUrl;

    /** 相当于一个标志,可以随意 */
    @Value("${cas.client-name}")
    private String clientName;
    /**
     *  pac4j配置
     * @param casClient
     * @param shiroSessionStore
     * @return
     */
    @Bean()
    public Config config(CasClient casClient, CustomShiroSessionStore shiroSessionStore) {
        Config config = new Config(casClient);
        config.setSessionStore(shiroSessionStore);
        return config;
    }

    /**
     * 自定义存储
     * @return
     */
    @Bean
    public CustomShiroSessionStore shiroSessionStore(){
        return CustomShiroSessionStore.INSTANCE;
    }

    /**
     * cas 客户端配置
     * @param casConfig
     * @return
     */
    @Bean
    public CasClient casClient(CasConfiguration casConfig){
        CasClient casClient = new CustomCasClient(casConfig);
        //客户端回调地址
        casClient.setCallbackUrl(projectUrl + "/callback?client_name=" + clientName);
        casClient.setName(clientName);
        return casClient;
    }

    /**
     * 请求cas服务端配置
     * @param casLogoutHandler
     */
    @Bean
    public CasConfiguration casConfig(){
        final CasConfiguration configuration = new CasConfiguration();
        //CAS server登录地址
        configuration.setLoginUrl(casServerUrl + "/login");
        //CAS 版本,默认为 CAS30,我们使用的是 CAS20
        configuration.setProtocol(CasProtocol.CAS20);
        configuration.setAcceptAnyProxy(true);
        configuration.setPrefixUrl(casServerUrl + "/");
        configuration.setLogoutHandler(new DefaultCasLogoutHandler<WebContext>());
        return configuration;
    }


}

创建ShiroConfig

package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.filter.CallbackFilter;
import io.buji.pac4j.filter.LogoutFilter;
import io.buji.pac4j.filter.SecurityFilter;
import io.buji.pac4j.subject.Pac4jSubjectFactory;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;

import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
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.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.pac4j.core.config.Config;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.Ordered;
import org.springframework.web.filter.DelegatingFilterProxy;

@Configuration
public class ShiroConfig {

	/** 项目工程路径 */
    @Value("${cas.project.url}")
    private String projectUrl;

    /** 项目cas服务路径 */
    @Value("${cas.server.url}")
    private String casServerUrl;

    /** 客户端名称 */
    @Value("${cas.client-name}")
    private String clientName;
    
    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(Pac4jSubjectFactory subjectFactory, SessionManager sessionManager, CasRealm casRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(casRealm);
        manager.setSubjectFactory(subjectFactory);
        manager.setSessionManager(sessionManager);
        return manager;
    }
    @Bean
    public CasRealm casRealm(){
        CasRealm realm = new CasRealm();
        // 使用自定义的realm
        realm.setClientName(clientName);
        realm.setCachingEnabled(false);
        //暂时不使用缓存
        realm.setAuthenticationCachingEnabled(false);
        realm.setAuthorizationCachingEnabled(false);
        //realm.setAuthenticationCacheName("authenticationCache");
        //realm.setAuthorizationCacheName("authorizationCache");
        return realm;
    }
    /**
     * 使用 pac4j 的 subjectFactory
     * @return
     */
    @Bean
    public Pac4jSubjectFactory subjectFactory(){
        return new Pac4jSubjectFactory();
    }

    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean() {
        FilterRegistrationBean<DelegatingFilterProxy> filterRegistration = new FilterRegistrationBean<DelegatingFilterProxy>();
        filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
        //  该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
        filterRegistration.addInitParameter("targetFilterLifecycle", "true");
        filterRegistration.setEnabled(true);
        filterRegistration.addUrlPatterns("/*");
        filterRegistration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);
        return filterRegistration;
    }

    /**
     * 加载shiroFilter权限控制规则(从数据库读取然后配置)
     * @param shiroFilterFactoryBean
     */
    private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
        /*下面这些规则配置最好配置到配置文件中 */
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/", "securityFilter");
        filterChainDefinitionMap.put("/application/**", "securityFilter");
        filterChainDefinitionMap.put("/index", "securityFilter");
        filterChainDefinitionMap.put("/hello", "securityFilter");
        filterChainDefinitionMap.put("/userInfo", "customCasFilter");
        filterChainDefinitionMap.put("/callback", "callbackFilter");
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/**","anon");
        // filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    }

    
    

    /**
     * shiroFilter
     * @param securityManager
     * @param config
     * @return
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, Config config) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        // 添加casFilter到shiroFilter中
        loadShiroFilterChain(shiroFilterFactoryBean);
        Map<String, Filter> filters = new HashMap<>(4);
        //cas 资源认证拦截器
        SecurityFilter securityFilter = new SecurityFilter();
        securityFilter.setConfig(config);
        securityFilter.setClients(clientName);
        filters.put("securityFilter", securityFilter);
       //cas 自定义资源认证拦截器--允许未登录返回,但是如果在其他项目中已经登录的(cookie中已经包含了tgc)又需要他能够显示用户信息
        CustomCasFilter customCasFilter = new CustomCasFilter();
        customCasFilter.setConfig(config);
        customCasFilter.setClients(clientName);
        filters.put("customCasFilter", customCasFilter);
        //cas 认证后回调拦截器
        CallbackFilter callbackFilter = new CustomCallbackFilter();
        callbackFilter.setConfig(config);
        callbackFilter.setDefaultUrl(projectUrl);
        filters.put("callbackFilter", callbackFilter);
        // 注销 拦截器
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setConfig(config);
        logoutFilter.setCentralLogout(true);
        logoutFilter.setLocalLogout(true);
        //添加logout后  跳转到指定url  url的匹配规则  默认为 /.*;  
        logoutFilter.setLogoutUrlPattern(".*");
        logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName);
        filters.put("logout",logoutFilter);
        shiroFilterFactoryBean.setFilters(filters);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SessionDAO sessionDAO(){
        return new MemorySessionDAO();
    }

    /**
     * 自定义cookie名称
     * @return
     */
    @Bean
    public SimpleCookie sessionIdCookie(){
        SimpleCookie cookie = new SimpleCookie("sid");
        cookie.setMaxAge(-1);
        cookie.setPath("/");
        cookie.setHttpOnly(false);
        return cookie;
    }

    @Bean
    public DefaultWebSessionManager sessionManager(SimpleCookie sessionIdCookie, SessionDAO sessionDAO){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionIdCookie(sessionIdCookie);
        sessionManager.setSessionIdCookieEnabled(true);
        //30分钟
        sessionManager.setGlobalSessionTimeout(180000);
        sessionManager.setSessionDAO(sessionDAO);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        return sessionManager;
    }

    /**
     * 下面的代码是添加注解支持
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
    
    @Bean
    public FilterRegistrationBean<CustomContextThreadLocalFilter> casAssertionThreadLocalFilter(ShiroFilterFactoryBean shiroFilterFactoryBean) {
    	/**
    	 * 所有经过身份过滤拦截的请求、都需要经过CustomAssertionThreadLocalFilter 这个过滤器、
    	 */
    	Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
    	List<String> casUrls = new LinkedList<String>();
    	for (Entry<String, String> entry : filterChainDefinitionMap.entrySet()) {
			if("securityFilter".equals(entry.getValue())||"customCasFilter".equals(entry.getValue())){
				casUrls.add(entry.getKey());
			}
		}
        final FilterRegistrationBean<CustomContextThreadLocalFilter> assertionTLFilter = new FilterRegistrationBean<CustomContextThreadLocalFilter>();
        assertionTLFilter.setFilter(new CustomContextThreadLocalFilter());
        assertionTLFilter.setOrder(Ordered.LOWEST_PRECEDENCE);
        assertionTLFilter.setUrlPatterns(casUrls);
        return assertionTLFilter;
    }

}

创建自定义扩展类

package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.context.ShiroSessionStore;

import org.apache.shiro.session.Session;

public class ShiroProvidedSessionStore extends ShiroSessionStore {

    /**存储的TrackableSession,往后要操作时用这个session操作*/
    private Session session;

    public ShiroProvidedSessionStore(Session session) {
        this.session = session;
    }
    @Override
    protected Session getSession(final boolean createSession) {
        return session;
    }
}



package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.context.ShiroSessionStore;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.session.Session;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.context.session.SessionStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomShiroSessionStore implements SessionStore<J2EContext> {

	 private final static Logger logger = LoggerFactory.getLogger(ShiroSessionStore.class);

	 public final static CustomShiroSessionStore INSTANCE = new CustomShiroSessionStore();

	
	 /**
	     * Get the Shiro session (do not create it if it does not exist).
	     *
	     * @param createSession create a session if requested
	     * @return the Shiro session
	     */
	    protected Session getSession(final boolean createSession) {
	        return SecurityUtils.getSubject().getSession(createSession);
	    }

	    @Override
	    public String getOrCreateSessionId(final J2EContext context) {
	        final Session session = getSession(false);
	        if (session != null) {
	            return session.getId().toString();
	        }
	        return null;
	    }

	    @Override
	    public Object get(final J2EContext context, final String key) {
	        final Session session = getSession(false);
	        if (session != null) {
	            return session.getAttribute(key);
	        }
	        return null;
	    }

	    @Override
	    public void set(final J2EContext context, final String key, final Object value) {
	        final Session session = getSession(true);
	        if (session != null) {
	            try {
	                session.setAttribute(key, value);
	            } catch (final UnavailableSecurityManagerException e) {
	                logger.warn("Should happen just once at startup in some specific case of Shiro Spring configuration", e);
	            }
	        }
	    }

	    @Override
	    public boolean destroySession(final J2EContext context) {
	        getSession(true).stop();
	        return true;
	    }


	@Override
	public Object getTrackableSession(J2EContext context) {
		return getSession(true);
	}

	@Override
	public SessionStore<J2EContext> buildFromTrackableSession(
			J2EContext context, Object trackableSession) {
		  if(trackableSession != null) {
	            return new ShiroProvidedSessionStore((Session) trackableSession);
	        }
	        return null;
	}

	@Override
	public boolean renewSession(J2EContext context) {
		return false;
	}

}

package com.spean.shiro_cas.config.shiro;

import org.pac4j.cas.client.CasClient;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.core.context.Pac4jConstants;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.redirect.RedirectAction;
import org.pac4j.core.util.CommonHelper;

public class CustomCasClient extends CasClient {

	public CustomCasClient() {
		super();
	}
	public CustomCasClient(CasConfiguration configuration) {
		super(configuration);
	}
	@Override
    public RedirectAction getRedirectAction(WebContext context) {
        this.init();
        if (getAjaxRequestResolver().isAjax(context)) {
            this.logger.info("AJAX request detected -> returning the appropriate action");
            RedirectAction action = getRedirectActionBuilder().redirect(context);
            this.cleanRequestedUrl(context);
            return getAjaxRequestResolver().buildAjaxResponse(action.getLocation(), context);
        } else {
            final String attemptedAuth = (String)context.getSessionStore().get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX);
            if (CommonHelper.isNotBlank(attemptedAuth)) {
                this.cleanAttemptedAuthentication(context);
                this.cleanRequestedUrl(context);
                //这里按自己需求处理,默认是返回了401,我在这边改为跳转到cas登录页面
                //throw HttpAction.unauthorized(context);
                return this.getRedirectActionBuilder().redirect(context);
            } else {
                return this.getRedirectActionBuilder().redirect(context);
            }
        }
    }

    private void cleanRequestedUrl(WebContext context) {
        SessionStore<WebContext> sessionStore = context.getSessionStore();
        if (sessionStore.get(context, Pac4jConstants.REQUESTED_URL) != null) {
            sessionStore.set(context, Pac4jConstants.REQUESTED_URL, "");
        }

    }

    private void cleanAttemptedAuthentication(WebContext context) {
        SessionStore<WebContext> sessionStore = context.getSessionStore();
        if (sessionStore.get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX) != null) {
            sessionStore.set(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX, "");
        }

    }
}

package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.realm.Pac4jRealm;
import io.buji.pac4j.subject.Pac4jPrincipal;
import io.buji.pac4j.token.Pac4jToken;

import java.util.List;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.pac4j.core.profile.CommonProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CasRealm extends Pac4jRealm {
	
	Logger logger = LoggerFactory.getLogger(CasRealm.class);

	private String clientName;

	public String getClientName() {
		return clientName;
	}

	public void setClientName(String clientName) {
		this.clientName = clientName;
	}

	/**
	 * 认证
	 * 
	 * @param authenticationToken
	 * @return
	 * @throws AuthenticationException
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authenticationToken)
			throws AuthenticationException {
		final Pac4jToken pac4jToken = (Pac4jToken) authenticationToken;
		final List<CommonProfile> commonProfileList = pac4jToken.getProfiles();
		final CommonProfile commonProfile = commonProfileList.get(0);
		logger.info("单点登录返回的信息" + commonProfile.toString());
		final Pac4jPrincipal principal = new Pac4jPrincipal(commonProfileList,
				getPrincipalNameAttribute());
		final PrincipalCollection principalCollection = new SimplePrincipalCollection(
				principal, getName());
		return new SimpleAuthenticationInfo(principalCollection,
				commonProfileList.hashCode());
	}

	/**
	 * 授权/验权(todo 后续有权限在此增加)
	 * 
	 * @param principals
	 * @return
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();
		authInfo.addStringPermission("user");
		return authInfo;
	}
}

package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.filter.CallbackFilter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * sso登录回调 返回st,单点登录也会post到这个地址,
 * 验证st后跳转
 * http://localhost/callback?client_name=demoClient&ticket=ST-54-AKN6qLpOlwlMgjtP22Yf-sso.foxitreader.cn
 * @author ssss
 *
 */
public class CustomCallbackFilter extends CallbackFilter {

	@Override
	public void doFilter(ServletRequest servletRequest,
			ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		super.doFilter(servletRequest, servletResponse, filterChain);
	}
	
}

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Spring Boot是一个用于快速开发Java应用程序的开源框架,Shiro是一个强大且易于使用的Java安全框架,Redis是一个开源的内存数据库。结合使用这些技术可以实现单点登录功能。 在Spring Boot中使用Shiro来处理认证和授权,可以通过配置Shiro的Realm来实现用户的登录认证和权限控制。将用户的信息存储在Redis中,利用Redis的持久化特性来实现用户登录状态的共享和存储。 首先,在Spring Boot项目的配置文件中配置Redis的连接信息,以便连接到Redis数据库。 然后,创建一个自定义的Shiro的Realm,在其中重写认证和授权的方法。在认证方法中,将用户的登录信息存储到Redis中,以便其他服务可以进行验证。在授权方法中,根据用户的角色和权限进行相应的授权操作。 接着,在Spring Boot项目的配置类中配置Shiro的相关设置,包括Realm、Session管理器、Cookie管理器等。 最后,可以在Controller层中使用Shiro的注解来标记需要进行认证和授权的接口,以确保只有登录后且具备相应权限的用户才能访问这些接口。 总的来说,通过使用Spring Boot、Shiro和Redis的组合,可以实现单点登录的功能。用户在登录后,将登录信息存储到Redis中,其他服务可以通过验证Redis中的数据来判断用户的登录状态。同时,Shiro提供了强大的认证和授权功能,可以确保只有具备相应权限的用户才能访问受保护的接口。这些功能的具体实现可以通过深入研究Spring Boot、Shiro和Redis的源码来了解。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思无邪1990

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值