SpringSecurity 自定义用户 角色 资源权限控制
package com.joyen.learning.security; import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List; import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; /**
* 在这个类中,从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等
* @author fwj
*
*/
public class MyUserDetailService extends JdbcDaoSupport implements UserDetailsService { private String authoritiesByUsernameQuery;
private String usersByUsernameQuery; protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
List<MyUser> users = loadUsersByUsername(username); if (users==null || users.size() == 0) {
logger.debug("Query returned no results for user '" + username + "'"); throw new UsernameNotFoundException(
messages.getMessage("JdbcDaoImpl.notFound", new Object[]{username}, "Username {0} not found"), username);
} MyUser user = users.get(0);
List<GrantedAuthority> dbAuths = loadUserAuthorities(user.getUsername()); if (dbAuths == null || dbAuths.size() == 0) {
logger.debug("User '" + username + "' has no authorities and will be treated as 'not found'"); throw new UsernameNotFoundException(
messages.getMessage("JdbcDaoImpl.noAuthority",
new Object[] {username}, "User {0} has no GrantedAuthority"), username);
} return createUserDetails(username,user,dbAuths); } protected List<MyUser> loadUsersByUsername(String username) { return getJdbcTemplate().query(usersByUsernameQuery, new String[] {username}, new RowMapper<MyUser>() {
public MyUser mapRow(ResultSet rs, int rowNum) throws SQLException {
String username = rs.getString(1);
String password = rs.getString(2);
String email = rs.getString(3);
boolean enabled = rs.getBoolean(4);
return new MyUser(username, password, email, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES);
} });
} protected List<GrantedAuthority> loadUserAuthorities(String username) {
return getJdbcTemplate().query(authoritiesByUsernameQuery, new String[] {username}, new RowMapper<GrantedAuthority>() {
public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
String roleName = rs.getString(2);
GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName); return authority;
}
});
} protected UserDetails createUserDetails(String username, MyUser userFromUserQuery,
List<GrantedAuthority> combinedAuthorities) {
String returnUsername = userFromUserQuery.getUsername(); return new MyUser(returnUsername, userFromUserQuery.getPassword(), userFromUserQuery.getEmail(), userFromUserQuery.isEnabled(),
true, true, true, combinedAuthorities);
} public String getAuthoritiesByUsernameQuery() {
return authoritiesByUsernameQuery;
} public void setAuthoritiesByUsernameQuery(String authoritiesByUsernameQuery) {
this.authoritiesByUsernameQuery = authoritiesByUsernameQuery;
} public String getUsersByUsernameQuery() {
return usersByUsernameQuery;
} public void setUsersByUsernameQuery(String usersByUsernameQuery) {
this.usersByUsernameQuery = usersByUsernameQuery;
} }
MyUserDetailService
package com.joyen.learning.security; 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 final String email; public MyUser(String username, String password, String email, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, authorities);
// TODO Auto-generated constructor stub
this.email = email;
} public String getEmail() {
return email;
} }
MyUser
package com.joyen.learning.security; import java.io.IOException; import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return securityMetadataSource;
} public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
} @Override
public Class<? extends Object> getSecureObjectClass() {
return FilterInvocation.class;
} @Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
} public void destroy() {
// TODO Auto-generated method stub } public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi); } public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub } public void invoke(FilterInvocation fi) throws IOException, ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
} }
MyFilterSecurityInterceptor
package com.joyen.learning.security; import java.util.Collection;
import java.util.Iterator; import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.FilterInvocation; /**
* 决策类
* 如果不存在对该资源的定义,直接放行;否则,如果找到正确的角色,即认为拥有权限,并放行
* @author fwj
*
*/
public class MyAccessDecisionManager implements AccessDecisionManager { public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException { if(configAttributes == null){
return ;
} FilterInvocation fi = (FilterInvocation)object;
System.out.println("=============request url==========="+fi.getRequestUrl()); //object is a URL.
Iterator<ConfigAttribute> ite=configAttributes.iterator();
while(ite.hasNext()){
ConfigAttribute ca=ite.next();
String needRole=((SecurityConfig)ca).getAttribute();
for(GrantedAuthority ga:authentication.getAuthorities()){
if(needRole.equals(ga.getAuthority())){ //ga is user's role.
return;
}
}
}
throw new AccessDeniedException("no right"); } public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return true;
} public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
} }
MyAccessDecisionManager
package com.joyen.learning.security; import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map; import javax.sql.DataSource; import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.AntUrlPathMatcher;
import org.springframework.security.web.util.UrlMatcher; /**
*
* 此类在初始化时,应该取到所有资源及其对应角色的定义
*
* @author fuwenjun
*
*/
public class MyInvocationSecurityMetadataSource extends JdbcDaoSupport implements
FilterInvocationSecurityMetadataSource { private String resourceQuery;
private UrlMatcher urlMatcher = new AntUrlPathMatcher();
private static Map<String, Collection<ConfigAttribute>> resourceMap = null; public MyInvocationSecurityMetadataSource(DataSource dataSource,String resourceQuery) {
this.setDataSource(dataSource);
this.resourceQuery = resourceQuery;
this.loadResourceDefine();
} private void loadResourceDefine(){
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
List<ResourceRole> list = getJdbcTemplate().query(resourceQuery, new RowMapper<ResourceRole>() {
public ResourceRole mapRow(ResultSet rs, int rowNum) throws SQLException {
String url = rs.getString(1);
String role = rs.getString(2);
return new ResourceRole(url, role);
}
});
ConfigAttribute ca = null;
Collection<ConfigAttribute> cca = null;
for (ResourceRole resourceRole : list) {
if(resourceMap.containsKey(resourceRole.getUrl())){
ca = new SecurityConfig(resourceRole.getRole());
resourceMap.get(resourceRole.getUrl()).add(ca);
}else{
ca = new SecurityConfig(resourceRole.getRole());
cca = new ArrayList<ConfigAttribute>();//首次创建一个新的configattribute集合
cca.add(ca);
resourceMap.put(resourceRole.getUrl(), cca);
}
} } public Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException {
String url = ((FilterInvocation)object).getRequestUrl();
Iterator<String> ite = resourceMap.keySet().iterator();
while (ite.hasNext()) {
String resURL = ite.next();
if (urlMatcher.pathMatchesUrl(url, resURL)) {
return resourceMap.get(resURL);
}
}
return null;
} public Collection<ConfigAttribute> getAllConfigAttributes() {
// TODO Auto-generated method stub
return null;
} public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
} }
MyInvocationSecurityMetadataSource
package com.joyen.learning.security; public class ResourceRole { private String url;
private String role; public ResourceRole(String url, String role) {
this.url = url;
this.role = role;
} public String getUrl() {
return url;
} public String getRole() {
return role;
}
}
ResourceRole
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd"> <http access-denied-page="/403" auto-config="false"><!-- 当访问被拒绝时,会转到403.jsp -->
<intercept-url pattern="/login" filters="none" />
<form-login login-page="/login"
authentication-failure-url="/login?error=true"
default-target-url="/index"/>
<logout logout-success-url="/login" />
<!-- 增加一个filter,这点与Acegi是不一样的,不能修改默认的filter了,这个filter位于FILTER_SECURITY_INTERCEPTOR之前 -->
<custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myFilter" />
</http> <!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->
<authentication-manager alias="authenticationManager">
<authentication-provider
user-service-ref="myUserDetailService">
<!-- 如果用户的密码采用加密的话,可以加点“盐”
<password-encoder hash="md5" />
-->
</authentication-provider>
</authentication-manager> <!-- 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性,我们的所有控制将在这三个类中实现,解释详见具体配置 -->
<beans:bean id="myFilter" class="com.joyen.learning.security.MyFilterSecurityInterceptor">
<beans:property name="authenticationManager"
ref="authenticationManager" />
<beans:property name="accessDecisionManager"
ref="myAccessDecisionManagerBean" />
<beans:property name="securityMetadataSource"
ref="securityMetadataSource" />
</beans:bean> <beans:bean id="myUserDetailService"
class="com.joyen.learning.security.MyUserDetailService">
<beans:property name="dataSource" ref="dataSource"></beans:property>
<beans:property name="usersByUsernameQuery" value="select username,password,email,enabled from user where username = ?"></beans:property>
<beans:property name="authoritiesByUsernameQuery" value="SELECT u.username,r.name
FROM user u,roleuser ru, role r
WHERE u.id = ru.userid
AND ru.roleid = r.id
AND u.username = ?"></beans:property>
</beans:bean> <!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<beans:bean id="myAccessDecisionManagerBean"
class="com.joyen.learning.security.MyAccessDecisionManager">
</beans:bean> <!-- 资源源数据定义,即定义某一资源可以被哪些角色访问 -->
<beans:bean id="securityMetadataSource"
class="com.joyen.learning.security.MyInvocationSecurityMetadataSource">
<beans:constructor-arg ref="dataSource"></beans:constructor-arg>
<beans:constructor-arg type="java.lang.String" value="select rce.url, r.name from role r inner join roleresource rrce on r.id = rrce.roleid inner join resource rce on rrce.resourceid = rce.id"></beans:constructor-arg>
</beans:bean>
</beans:beans>
spring-security.xml