使用Spring Security访问CAS发布的属性

时间:2015-11-24 21:18:07

标签: spring-security cas

我很难弄清楚如何使用Spring Security和Spring MVC在servlet中访问CAS发布的属性。传统上,在Spring-less实现中,我会做这样的事情

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
   // Gets the user ID from CAS
   AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
   final Map<String, Object> attributes = principal.getAttributes();
   String userId = (String) attributes.get("userid");

   // ...
}

使用Spring MVC创建servlet但没有Spring Security时,访问属性似乎没有区别:

@RequestMapping("/")
public String welcome(HttpServletRequest request)
{
   // Get the user ID from CAS
   AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();;
   final Map<String, Object> attributes = principal.getAttributes();
   userId = (String) attributes.get("userid");

   // ...
}

但是,在实施Spring Security后,request.getUserPrincipal()会返回CasAuthenticationToken而不是AttributePrincipal。根据我的注意,这里没有任何可检索对象和数据包含任何CAS发布的属性。

经过一番调查后,我注意到提到GrantedAuthorityFromAssertionAttributesUserDetailsService类的内容,所以我从

更改了我的安全上下文.xml
<security:user-service id="userService">
  <security:user name="user" password="user" authorities="ROLE_ADMIN,ROLE_USER" />
</security:user-service>

<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
  <property name="authenticationUserDetailsService">
    <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
      <constructor-arg ref="userService" />
    </bean>
  </property>
  <property name="serviceProperties" ref="serviceProperties" />
  <property name="ticketValidator">
    <bean class="org.jasig.cas.client.validation.Saml11TicketValidator">
      <constructor-arg value="https://localhost:8443/cas" />
    </bean>
  </property>
  <property name="key" value="casAuthProviderKey" />
</bean>

<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
  <property name="authenticationUserDetailsService">
    <bean class="org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService">
      <constructor-arg>
        <list>
          <value>userid</value>
        </list>
      </constructor-arg>
    </bean>
  </property>
  <property name="serviceProperties" ref="serviceProperties" />
  <property name="ticketValidator">
    <bean class="org.jasig.cas.client.validation.Saml11TicketValidator">
      <constructor-arg value="https://localhost:8443/cas" />
    </bean>
  </property>
  <property name="key" value="casAuthProviderKey" />
</bean>

然后,通过一个更加迂回的方法,我可以通过这样的方式访问userid属性:

@RequestMapping("/")
public String welcome(HttpServletRequest request)
{
   CasAuthenticationToken principal = (CasAuthenticationToken) request.getUserPrincipal();
   UserDetails userDetails = principal.getUserDetails();
   Collection<SimpleGrantedAuthority> authorities = (Collection<SimpleGrantedAuthority>) userDetails.getAuthorities();
   Iterator<SimpleGrantedAuthority> it = authorities.iterator();

   String userid = it.next().getAuthority();

   // ...
}

然而,除了比以前的实现稍微冗长之外,似乎不可能支持来自CAS的地图多个属性(例如,如果CAS也发布了firstNamelastName属性)。

是否有更好的方法来设置安全上下文.xml以允许更轻松地访问这些属性,尤其是在我想要在Web应用程序中使用的倍数时?

1 个答案:

答案 0 :(得分:3)

我想我明白了。除了将权限设置为权限之外,如果您使用这些属性来确定权限(例如hasAuthority('username')),这可能很有用,似乎唯一的另一种方法是构建您自己的UserDetailsUserDetailsService课程。

例如,MyUser

package my.custom.springframework.security.userdetails;

import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

public class MyUser extends User
{
   private static final long serialVersionUID = 1L;

   private String id;
   private String lastName;
   private String firstName;

   public MyUser(
         String username,
         String password,
         String id,
         String lastName,
         String firstName,
         Collection<? extends GrantedAuthority> authorities)
   {
      super(username, password, authorities);
      this.id = id;
      this.lastName = lastName;
      this.firstName = firstName;
   }

   public String getId()
   {
      return id;
   }

   public String getLastName()
   {
      return lastName;
   }

   public String getFirstName()
   {
      return firstName;
   }
}

然后,借用GrantedAuthorityFromAssertionAttributesUserDetailsServiceJdbcDaoImpl的一些结构,我创建了一个MyUserDetailsService

package my.custom.springframework.security.userdetails;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.sql.DataSource;

import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.cas.userdetails.AbstractCasAssertionUserDetailsService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

public final class MyUserDetailsService extends AbstractCasAssertionUserDetailsService
{
   public static final String DEF_USERS_BY_ID_QUERY = "select ?, id, last_name, first_name " +
         "from users " + "where id = ?";
   public static final String DEF_AUTHORITIES_BY_ID_QUERY = "select role " +
         "from roles join users on users.username = roles.username " +
         "where users.id = ?";

   private static final String NON_EXISTENT_PASSWORD_VALUE = "NO_PASSWORD";

   private JdbcTemplate jdbcTemplate;

   private String usersByIdQuery;
   private String authoritiesByIdQuery;

   public MyUserDetailsService(DataSource dataSource)
   {
      this.jdbcTemplate = new JdbcTemplate(dataSource);
      this.usersByIdQuery = DEF_USERS_BY_ID_QUERY;
      this.authoritiesByIdQuery = DEF_AUTHORITIES_BY_ID_QUERY;
   }

   protected MyUser loadUserDetails(Assertion assertion)
   {
      AttributePrincipal attributePrincipal = assertion.getPrincipal();
      String username = attributePrincipal.getName();
      String id = (String) attributePrincipal.getAttributes().get("userid");

      MyUser user = loadUser(username, id);

      Set<GrantedAuthority> dbAuthsSet = new HashSet<GrantedAuthority>();
      dbAuthsSet.addAll(loadUserAuthorities(id));
      List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);

      return createMyUser(username, user, dbAuths);
   }

   protected MyUser loadUser(String username, String id)
   {
      return jdbcTemplate.queryForObject(usersByIdQuery, new String[] { username, id },
            new RowMapper<MyUser>()
            {
               public MyUser mapRow(ResultSet rs, int rowNum) throws SQLException
               {
                  String username = rs.getString(1);
                  String id = rs.getString(2);
                  String lastName = rs.getString(3);
                  String firstName = rs.getString(4);
                  return new MyUser(username, NON_EXISTENT_PASSWORD_VALUE, id, lastName, firstName,
                        AuthorityUtils.NO_AUTHORITIES);
               }
            });
   }

   protected List<GrantedAuthority> loadUserAuthorities(String id)
   {
      return jdbcTemplate.query(authoritiesByIdQuery, new String[] { id },
            new RowMapper<GrantedAuthority>()
            {
               public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException
               {
                  // TODO Replace with rolePrefix variable
                  String roleName = "ROLE_" + rs.getString(1);
                  return new SimpleGrantedAuthority(roleName);
               }
            });
   }

   protected MyUser createMyUser(String username,
         MyUser userFromUserQuery, List<GrantedAuthority> combinedAuthorities)
   {
      return new MyUser(username, userFromUserQuery.getPassword(),
            userFromUserQuery.getId(), userFromUserQuery.getLastName(), userFromUserQuery.getFirstName(),
            combinedAuthorities);
   }
}

最后,我在我的authenticationUserDetailsService中设置casAuthenticationProvider以使用此类,从我的容器传入全局数据源(在本例中为Tomcat 6):

...
<property name="authenticationUserDetailsService">
  <bean class="my.custom.springframework.security.userdetails.MyUserDetailsService">
    <constructor-arg>
      <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/my/conn"/>
    </constructor-arg>
  </bean>
</property>
...