具有 CAS 身份验证和自定义授权的 Spring Security

Posted

技术标签:

【中文标题】具有 CAS 身份验证和自定义授权的 Spring Security【英文标题】:Spring Security with CAS authentication and custom authorization 【发布时间】:2012-10-25 19:04:56 【问题描述】:

我是 Spring Security 和 CAS 的新手。我让 SSO 部分正常工作,但正在努力让授权部分正常工作。我按照这个来实现自定义授权

http://www.theserverside.com/tip/-Spring-Security-Customizing-Your-User-and-Authorization-in

应该何时调用 loadUserDetails。我在这里设置了一个断点,这个方法永远不会被调用。

任何帮助将不胜感激。看起来我缺少调用授权的配置。

我想让我的自定义用户对象填充权限并驻留在会话中,以便我可以在任何阶段检查用户角色。

当我登录日志时,这里是我登录后发生的步骤

    1. /indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas at position 1 of 8 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'  (FilterChainProxy:329)

No HttpSession currently exists  (HttpSessionSecurityContextRepository:127)

No SecurityContext was available from the HttpSession: null. A new one will be created.  (HttpSessionSecurityContextRepository:85)

2. /indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas at position 2 of 8 in additional filter chain; firing Filter: 'CasAuthenticationFilter'  (FilterChainProxy:329)

3. /indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas at position 3 of 8 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'  (FilterChainProxy:329)

4. /indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas at position 4 of 8 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'  (FilterChainProxy:329)

5. /indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas at position 5 of 8 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'  (FilterChainProxy:329)

Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@6faa93c2: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffffe21a: RemoteIpAddress: 192.168.0.124; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'  (AnonymousAuthenticationFilter:102)

6. /indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas at position 6 of 8 in additional filter chain; firing Filter: 'SessionManagementFilter'  (FilterChainProxy:329)

7. /indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas at position 7 of 8 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'  (FilterChainProxy:329)

8. /indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas at position 8 of 8 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'  (FilterChainProxy:329)

Checking match of request : '/indexcasepackoptions'; against '/login/**'  (AntPathRequestMatcher:103)

Secure object: FilterInvocation: URL: /indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas; Attributes: [ROLE_ANONYMOUS]  (FilterSecurityInterceptor:193)

Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@6faa93c2: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffffe21a: RemoteIpAddress: 
192.168.0.124; SessionId: null; Granted Authorities: ROLE_ANONYMOUS  (FilterSecurityInterceptor:298)

Voter: org.springframework.security.access.vote.RoleVoter@7077602d, returned: 1  (AffirmativeBased:65)

Authorization successful  (FilterSecurityInterceptor:214)

RunAsManager did not change Authentication object  (FilterSecurityInterceptor:226)

/indexCasePackOptions?ticket=ST-4295-4eFUHVziBcmVaOd3bifl-cas reached end of additional filter chain; proceeding with original chain  (FilterChainProxy:315)

No Proxy Ticket found for   (ProxyGrantingTicketStorageImpl:77)

SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.  (HttpSessionSecurityContextRepository:269)

Chain processed normally  (ExceptionTranslationFilter:115)

SecurityContextHolder now cleared, as request processing completed  (SecurityContextPersistenceFilter:97)    

我的自定义用户详情

package com.creata.domain;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class User implements Serializable, UserDetails 

private static final long serialVersionUID = 1L;
private Collection<SimpleGrantedAuthority> authorities;
private final String username;
private String password;

public User(String username) 
    this.username=username;


@Override
public Collection<SimpleGrantedAuthority> getAuthorities() 
    return authorities;


public void setUserAuthorities(List<String> roles) 
    List<SimpleGrantedAuthority> listOfAuthorities = new ArrayList<SimpleGrantedAuthority>();
    for (String role : roles) 
        listOfAuthorities.add(new SimpleGrantedAuthority(role));
    
    authorities = listOfAuthorities;


@Override
public String getPassword() 
    return this.password;


@Override
public String getUsername() 
    return username;


@Override
public boolean isAccountNonExpired() 
    return false;


@Override
public boolean isAccountNonLocked() 
    return false;


@Override
public boolean isCredentialsNonExpired() 
    return false;


@Override
public boolean isEnabled() 
    return true;

我的自定义用户服务

package com.creata.security.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.creata.domain.User;

public class CustomUserService implements UserDetailsService, AuthenticationUserDetailsService 

@Override
public UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException 
    System.out.println("loadUserByUsername called here ");
    User currentUser = new User("Reena");
    //Collection<SimpleGrantedAuthority> authorities  = currentUser.getAuthorities();
    List<String> roles = new ArrayList<String>();
    roles.add("ROLE_Admin");
    roles.add("ROLE_CPAD-Maintenance");
    currentUser.setUserAuthorities(roles);
    //authorities.add(new SimpleGrantedAuthority("PRIVILEGE_HOME"));
    return currentUser;


@Override
public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException
    User currentUser = new User("Reena");
    List<String> roles = new ArrayList<String>();
    roles.add("ROLE_Admin");
    roles.add("ROLE_CPAD-Maintenance");
    currentUser.setUserAuthorities(roles);
    //authorities.add(new SimpleGrantedAuthority("PRIVILEGE_HOME"));
    return currentUser;


这是我的安全配置:

<beans:beans 
xmlns="http://www.springframework.org/schema/security" 
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" 
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:tx="http://www.springframework.org/schema/tx" 
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/security 
    http://www.springframework.org/schema/security/spring-security-3.1.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<http entry-point-ref="casEntryPoint">   
    <custom-filter position="CAS_FILTER" ref="casFilter" />
</http>

<authentication-manager alias="authenticationManager">
    <authentication-provider ref="casAuthenticationProvider" />
</authentication-manager>   

<beans:bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
    <beans:property name="authenticationManager" ref="authenticationManager"/>
</beans:bean>  

<beans:bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
    <beans:property name="loginUrl" value="https://abc.com/cas/login"/>
    <beans:property name="serviceProperties" ref="serviceProperties"/>
</beans:bean>

<beans:bean id="casAuthenticationProvider"  class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
    <beans:property name="authenticationUserDetailsService" ref="customUserService"/>
    <beans:property name="serviceProperties" ref="serviceProperties" />
    <beans:property name="ticketValidator">
    <beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
        <beans:constructor-arg index="0" value="https://portal.creata.com/cas" />
    </beans:bean>
    </beans:property>
    <beans:property name="key" value="an_id_for_this_auth_provider_only"/>
</beans:bean>

<beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
    <beans:property name="service"  value="https://mypc.abc.com/Spring_CPAD2/"/>
</beans:bean>

<beans:bean id="customUserService" class="com.creata.security.service.CustomUserService"/>  
</beans:beans>      

这是我的 web.xml

 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="true">
<display-name />

 <context-param>
    <param-name>serverName</param-name>
    <param-value>https://cpaus-rsingh2.creata.com</param-value>
</context-param>
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>    
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>    
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>    
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener> 
<filter>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter>
    <filter-name>CAS Authentication Filter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>https://abc.com/cas/login?AppName=app1</param-value>
    </init-param>
</filter>
<filter>
    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    <init-param>
        <param-name>casServerUrlPrefix</param-name>
        <param-value>https://abc.com/cas</param-value>
    </init-param>
</filter>
<filter>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Authentication Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter>
    <filter-name>ResponseOverrideFilter</filter-name>
    <filter-class>org.displaytag.filter.ResponseOverrideFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ResponseOverrideFilter</filter-name>
    <url-pattern>*.jsp</url-pattern>
  </filter-mapping>
<filter>
    <description>generated-persistence-filter</description>
    <filter-name>com_ibm_db2_jcc_DB2DriverFilter</filter-name>
    <filter-class>
        org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
    </filter-class>
    <init-param>
        <param-name>entityManagerFactoryBeanName</param-name>
        <param-value>com_ibm_db2_jcc_DB2Driver</param-value>
    </init-param>
</filter>
<filter>
    <description>generated-sitemesh-filter</description>
    <filter-name>Sitemesh Filter</filter-name>
    <filter-class>
        com.opensymphony.module.sitemesh.filter.PageFilter
    </filter-class>
</filter>
<!-- <filter>
    <description>generated-persistence-filter</description>
    <filter-name>MyEclipse_DerbyFilter</filter-name>
    <filter-class>
        org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
    </filter-class>
    <init-param>
        <param-name>entityManagerFactoryBeanName</param-name>
        <param-value>MyEclipse_Derby</param-value>
    </init-param>
</filter> -->
<filter-mapping>
    <filter-name>com_ibm_db2_jcc_DB2DriverFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>Sitemesh Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!-- <filter-mapping>
    <filter-name>MyEclipse_DerbyFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping> -->
<servlet>
    <description>generated-servlet</description>
    <servlet-name>Spring_CPAD2 Servlet</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:Spring_CPAD2-web-context.xml
        </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet>
    <description>generated-resources-servlet</description>
    <servlet-name>Resource Servlet</servlet-name>
    <servlet-class>
        org.springframework.js.resource.ResourceServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet>
    <description>generated-webflow-servlet</description>
    <servlet-name>webflow</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/config/Spring_CPAD2-webflow-config.xml
        </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>  
<servlet-mapping>
    <servlet-name>Resource Servlet</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>Spring_CPAD2 Servlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>webflow</servlet-name>
    <url-pattern>*.flow</url-pattern> 
</servlet-mapping>
<welcome-file-list>
    <welcome-file>jsp/intro.jsp</welcome-file>
</welcome-file-list>
    <jsp-config>
        <taglib>
            <taglib-uri>MyCustomTags</taglib-uri>
            <taglib-location>/WEB-INF/tlds/MyCustomTags.tld</taglib-location>
        </taglib>
    </jsp-config>

【问题讨论】:

我应该调试代码的客户端,看看为什么我的自定义 UserDetails 没有被调用,还是发生在 CAS 服务器端?抱歉,如果这听起来像是一个基本问题,我无法解决此问题并感到困惑。在我的控制器中 SecurityContextHolder.getContext().getAuthentication().getPrincipal();让我匿名用户。如何获取已实现的 UserDetails?有人请给我亮光 @rodrigoap 我在***.com/questions/1322135/… 看到了你的回答,你能帮我解决我的问题吗?我需要创建一个预认证过滤器吗?我的方法不正确吗。我错过了什么? 这个问题你解决了吗?我遇到了同样的问题,我无法解决,如果你解决了,请发布答案 @rdev 我也有同样的问题。你罚款解决方案吗?如果没有,您是否知道任何替代解决方案?谢谢 【参考方案1】:

CasAuthenticationProvider 仅在身份验证为空时调用 userDetailsS​​ervice

我建议在 CasAuthenticationProvider.java 中放置一个调试点(在下面的代码中参考我的 cmets)

 public class CasAuthenticationProvider 
 public Authentication authenticate(Authentication authentication) throws AuthenticationException 

    CasAuthenticationToken result = null;

    if (stateless) 
        // Try to obtain from cache
        result = statelessTicketCache.getByTicketId(authentication.getCredentials().toString());
    

    if (result == null) 
        //following line calls userDetailsService. I would recommend to put a debug point here 
        ///if it is not calling userDetailsService, that means result is not null

        result = this.authenticateNow(authentication);
        result.setDetails(authentication.getDetails());
    

    if (stateless) 
        // Add to cache
        statelessTicketCache.putTicketInCache(result);
    

    return result;

您可以从http://mvnrepository.com/artifact/org.springframework.security/spring-security-cas-client 下载源代码并将其附加到eclipse。

【讨论】:

感谢您查看我的问题,我按照您的建议设置了断点,但是当我登录时,不会调用此方法。还有什么我可以尝试的吗? @Saurabh 我在static.springsource.org/spring-security/site/docs/3.1.x/… 中读到,要使 CAS 运行,ExceptionTranslationFilter 必须将其 authenticationEntryPoint 属性设置为 CasAuthenticationEntryPoint bean。我添加了 但 CasAuthenticationProvider 仍然不被调用 @rsingh - 从日志中,我注意到用户已经登录 - 先前经过身份验证:org.springframework.security.authentication.AnonymousAuthenticationToken@6faa93c2:主体:anonymousUser;凭证:[受保护];已认证:真实;详细信息:org.springframework.security.web.authentication.WebAuthenticationDetails@ffffe21a:RemoteIpAddress:192.168.0.124;会话ID:空;授予权限:ROLE_ANONYMOUS (FilterSecurityInterceptor:298)。 CasAuthenticationProvider 没有被调用,因为用户已经过身份验证.. @rsingh - 看起来 url 受到“is_authenticated_anonymously”访问的保护......这就是为什么 AnonymousAuthenticationToken 允许用户在不登录的情况下访问该 url.. 感谢@Saurabh,这是否意味着我处于预授权状态?我需要采用不同的方法吗?或者如果是这样,我是否必须避免网址受到保护?抱歉所有这些问题...我尝试将拦截 url 放到登录页面上的 permitAll() 中,但我仍然收到相同的错误。

以上是关于具有 CAS 身份验证和自定义授权的 Spring Security的主要内容,如果未能解决你的问题,请参考以下文章

未指定 authenticationScheme,并且没有找到具有默认身份验证和自定义授权的 DefaultChallengeScheme

使用 CAS 进行身份验证和 LDAP 进行授权的 Spring 项目

具有基本身份验证和自定义 UserDetailsS​​ervice 的 Spring Boot OAuth2

CAS + Spring 安全性

Spring Security 和自定义 ws 身份验证

Spring OAuth2:支持 SSO 和自定义身份验证服务器的身份验证和资源访问