我很难弄清楚如何使用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
类的内容,所以我从
<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也发布了firstName
和lastName
属性)。
是否有更好的方法来设置安全上下文.xml以允许更轻松地访问这些属性,尤其是在我想要在Web应用程序中使用的倍数时?
答案 0 :(得分:3)
我想我明白了。除了将权限设置为权限之外,如果您使用这些属性来确定权限(例如hasAuthority('username')
),这可能很有用,似乎唯一的另一种方法是构建您自己的UserDetails
和UserDetailsService
课程。
例如,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;
}
}
然后,借用GrantedAuthorityFromAssertionAttributesUserDetailsService
和JdbcDaoImpl
的一些结构,我创建了一个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>
...