Springboot shiro整合cas

1.添加依赖包

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-cas</artifactId>
    <version>1.10.1</version>
</dependency>

2.自定义Realm添加supports

原先采用的用户名、密码登录,添加supports支持账号密码验证

@Override
public boolean supports(AuthenticationToken token) {
    return token instanceof UsernamePasswordToken;
}

3.添加CasRealm,ShiroCasConfig,CasFilter


import okhttp3.Response;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasAuthenticationException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.util.Date;
import java.util.List;
import java.util.Map;

public class MyShiroCasRealm extends CasRealm{
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private UserService userService;

    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroCasRealm.class);

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = (String) super.getAvailablePrincipal(principalCollection);
        User user = userRepository.findByUsername(username);
        if (null != user && user.getState() == 1) {
            SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
            // 业务处理

            return authzInfo;
        }
        return null;
    }

    /**
     * 1、CAS认证 ,验证用户身份
     * 2、将用户基本信息设置到会话中
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        String username = null;
        Map attributes = null;
        String cn = null;
        //将token转换为CasToken类型
        CasToken casToken = (CasToken) token;
        //如果token为空,则返回null
        if (token == null) {
            return null;
        }
        //获取票据
        String ticket = (String)casToken.getCredentials();
        //如果票据为空,则返回null
        if (!StringUtils.hasText(ticket)) {
            return null;
        }
        //确保票据验证器
        TicketValidator ticketValidator = ensureTicketValidator();
        try {
            //验证票据是否有效
            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
            //获取返回信息
            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
            username = casPrincipal.getName();
            logger.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", ticket, getCasServerUrlPrefix(), username);
            //获取配置的返回信息
            attributes = casPrincipal.getAttributes();
            cn = (String)attributes.get("cn");
        } catch (TicketValidationException e) {
            //如果验证异常,则抛出异常
            throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
        }
        
        User user = userRepository.findByUsername(username);
        String password = "******";
        if(null == user){ // 新建用户
            user = userService.createUser(username, cn==null?username:cn, password,0);
        }

        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        session.setAttribute("userId", user.getId());
        
        PrincipalCollection principalCollection = new SimplePrincipalCollection(attributes, getName());
            return new SimpleAuthenticationInfo(principalCollection, ticket);
    }
}
 

认证策略:

import lombok.RequiredArgsConstructor;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasSubjectFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.DelegatingFilterProxy;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Configuration
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfig.class)
@RequiredArgsConstructor
public class ShiroCasConfig {

    final SystemConfigRepository systemConfigRepository;

    String CasServerUrlPrefix = "http://127.0.0.1:8080/cas";
    String ServerName = "http://127.0.0.1:8081";

    // casFilter UrlPattern
    public static final String casFilterUrlPattern = "/shiro-cas";

    // Realm管理者
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator(){
        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
        //设置认证策略
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }
        
    @Bean
    public SecurityManager securityManager() {
        // 创建一个默认的Web安全经理
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 设置Realm管理者(需要在设置Realm之前)
        defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator());
        // 指定Shiro的实例
        // 在SecurityManager中新增多个realm
        List<Realm> realms = new ArrayList<>();
        realms.add(userRealm());
        realms.add(myShiroCasRealm());
        defaultWebSecurityManager.setRealms(realms);
        // 指定Subject工厂,实现Cas免登录的功能
        defaultWebSecurityManager.setSubjectFactory(new CasSubjectFactory());

        DefaultWebSessionManagersessionManager=newDefaultWebSessionManager();
        Collection<SessionListener>listeners=newArrayList<>();
        //配置监听
        listeners.add(sessionListener());
        sessionManager.setSessionListeners(listeners);
        defaultWebSecurityManager.setSessionManager(sessionManager);
        
        // 返回默认的Web安全经理
        return defaultWebSecurityManager;
    }
        
    @Bean("sessionListener")
    public ShiroSessionListener sessionListener(){
        return new ShiroSessionListener();
    }
        

    @Bean
    public MyShiroCasRealm myShiroCasRealm() {
        MyShiroCasRealm myShiroCasRealm = new MyShiroCasRealm();
        // 设置cas登录服务器地址的前缀
        myShiroCasRealm.setCasServerUrlPrefix(CasServerUrlPrefix);
        // 客户端回调地址,登录成功后的跳转的地址(自己的服务器)
        myShiroCasRealm.setCasService(ServerName+casFilterUrlPattern );
        return myShiroCasRealm;
    }

    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    // 注册单点登出的listener
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ServletListenerRegistrationBean servletListenerRegistrationBean() {
        //创建一个ServletListenerRegistrationBean对象
        ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
        //设置监听器为SingleSignOutHttpSessionListener
        bean.setListener(new SingleSignOutHttpSessionListener());
        //开启监听器
        bean.setEnabled(true);
        //返回bean对象
        return bean;
    }

    // 注册单点登出filter
    @Bean
    public FilterRegistrationBean registrationBean() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setName("registrationBean");
        registrationBean.setFilter(new SingleSignOutFilter());
        registrationBean.addUrlPatterns("/*"); //拦截所有的请求
        registrationBean.setEnabled(true);
        registrationBean.setOrder(10);//设置优先级
        return registrationBean;
    }

    // 注册DelegatingFilterProxy(Shiro)
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new DelegatingFilterProxy("shiroFilter")); //设置的shiro的拦截器 ShiroFilterFactoryBean
        bean.addInitParameter("targetFilterLifecycle", "true");
        bean.setEnabled(true);
        bean.addUrlPatterns("/*");
        return bean;
    }

    // 下面两个配置主要用来开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 设置代理方式,true是cglib的代理方式,false是普通的jdk代理方式
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor attributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    // 开启注解
    // 使用工厂模式,创建并初始化ShiroFilter
    // CAS过滤器
    // 定义一个CasFilter Bean,命名为casFilter
    @Bean(name = "casFilter")
    public CasFilter getCasFilter() {
        // 创建一个MyCasFilter实例
        MyCasFilter casFilter = new MyCasFilter();
        // 设置CasFilter的名称
        casFilter.setName("casFilter");
        // 设置CasFilter的可用状态
        casFilter.setEnabled(true);
        // 设置CasFilter失败时的URL
        casFilter.setFailureUrl(CasServerUrlPrefix + "/login?service=" + ServerName+"/proxyApi"+casFilterUrlPattern);
        // 设置CasFilter成功时的URL
        casFilter.setSuccessUrl(ServerName);
        // 返回CasFilter实例
        return casFilter;
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager, CasFilter casFilter) {
        // 创建ShiroFilterFactoryBean实例
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 设置SecurityManager
        factoryBean.setSecurityManager(securityManager);
        // 设置登录URL
        // 如果不设置,会自动寻找目录下的/login.jsp页面
        factoryBean.setLoginUrl(CasServerUrlPrefix + "/login?service=" + ServerName+"/proxyApi"+casFilterUrlPattern);
        // 设置成功访问URL
        factoryBean.setSuccessUrl(ServerName);
        // 设置无权限访问页面
//        factoryBean.setUnauthorizedUrl(shiroServerUrlPrefix);
        // 添加casFilter中,注意,casFilter需要放到shiroFilter的前面
        factoryBean.getFilters().put("casFilter", casFilter);
        factoryBean.getFilters().put("authc", new RestShiroAuthzFilter());

        // 加载Shiro过滤链
        loadShiroFilterChain(factoryBean);
        return factoryBean;
    }

    private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean) {
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

        filterChainDefinitionMap.put("/shiro-cas", "casFilter");

        //不拦截的请求
        filterChainDefinitionMap.put("/oapi/**", "anon");
        filterChainDefinitionMap.put("/api/anon/**", "anon");
        filterChainDefinitionMap.put("/api/service/deploy/**", "anon");

        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        // 此处将logout页面设置为anon,而不是logout,因为logout被单点处理,而不需要再被shiro的logoutFilter进行拦截
        filterChainDefinitionMap.put("/api/logout", "anon");

        //登录过的不拦截
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    }

}
 
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.springframework.context.annotation.Bean;

public class ShiroLifecycleBeanPostProcessorConfig {
    /**
     * Shiro生命周期处理器
     * 该类可以保证实现了org.apache.shiro.util.Initializable接口的shiro对象的init或者是destory方法被自动调用,
     * 而不用手动指定init-method或者是destory-method方法
     * 注意:如果使用了该类,则不需要手动指定初始化方法和销毁方法,否则会出错
     * @return
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}
 
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;

public class MyCasFilter extends CasFilter {
    @Autowired
    private UserRepository userRepository;


    private static final String TICKET_PARAMETER = "ticket";
    private static final Logger logger = LoggerFactory.getLogger(MyCasFilter.class);
    public MyCasFilter() {
    }

    @Override
    public AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        // 获取请求的ticket
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String ticket = getRequestTicket(httpRequest);
        if (StringUtils.isEmpty(ticket)) {
            return null;
        }
        return new CasToken(ticket);
    }

    /**
     * 拒绝除了option以外的所有请求
     **/
    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        logger.info("MyCasFilter isAccessAllowed");
        return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());
    }

    @Override
    public boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        // 获取ticket,如果不存在,直接返回false
        logger.info("MyCasFilter onAccessDenied");
        String ticket = getRequestTicket((HttpServletRequest) request);
        if (StringUtils.isEmpty(ticket)) {
            return false;
        }
        return this.executeLogin(request, response);
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        //获取AuthenticationToken实体
        AuthenticationToken token = createToken(request, response);
        logger.info("MyCasFilter executeLogin");
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            //执行分子系统的Shrio认证与授权
            subject.login(token);
            SecurityUtils.getSubject().getSession().setTimeout(2400*1000);
            SavedRequest shiroSavedRequest = (SavedRequest) SecurityUtils.getSubject().getSession(false).getAttribute("shiroSavedRequest");
            if (shiroSavedRequest != null) {
                //修改url地址为登录首页,否则会跳转到之前手输的地址容易404,由于没有set方法,所以使用反射
                Class<? extends Object> clazz  = shiroSavedRequest.getClass();
                Field requestURI = clazz.getDeclaredField("requestURI");
                requestURI.setAccessible(true);
                requestURI.set(shiroSavedRequest,this.getSuccessUrl());
            }
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }

    /**
     * 获取请求的ticket
     */
    private String getRequestTicket(HttpServletRequest httpRequest) {
        // 从参数中获取ticket
        String ticket = httpRequest.getParameter(TICKET_PARAMETER);
        if (StringUtils.isEmpty(ticket)) {
            // 如果为空的话,则从header中获取参数
            ticket = httpRequest.getHeader(TICKET_PARAMETER);
        }
        return ticket;
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
                                     ServletResponse response) throws Exception {
        logger.info("MyCasFilter onLoginSuccess");
        boolean flag = true;
        String successUrl = this.getSuccessUrl();
        if("".equals(successUrl)){
            successUrl = DEFAULT_SUCCESS_URL;
        }
        HttpServletResponse res = (HttpServletResponse) response;
        HttpServletRequest req = (HttpServletRequest) request;
        String username = req.getRemoteUser();
        if(null != username){
            User user = userRepository.findByUsername(username);
            // 业务处理
        }
        
        WebUtils.issueRedirect(request, res, successUrl, null, flag);
        //we handled the success redirect directly, prevent the chain from continuing:
        return false;
    }
}
 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值