最近在项目中遇到了关于spring security的问题,所以学习一下。
需要引入依赖:
org.springframework.security
spring-security-web
4.2.3.RELEASE
org.springframework
spring-web
org.springframework.security
spring-security-config
4.2.3.RELEASE
org.springframework
spring-web
用户身份认证:
自定义一个实现类MUserDetailsService来实现UserDetailsService接口。
其中需要实现一个loadUserByUsername方法,用来读取用户的角色。
在这里需要从数据库中通过用户名来查询用户的信息和用户所属的角色
其中MGrantedAuthority实现了GrantedAuthority接口,用于构建用户权限。
MUserDeatils实现了UserDeatils接口,用于存放用户信息与权限。
UserDetailsService在身份认证中的作用:
Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。验证身份就是加载响应的UserDetails,看看是否和用户输入的账号、密码、权限等信息匹配。此步骤由实现AuthenticationProvider的DaoAuthenticationProvider(它利用UserDetailsService验证用户名、密码和授权)处理。包含 GrantedAuthority 的 UserDetails对象在构建 Authentication对象时填入数据。
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
AdminUser adminUser = adminUserService.getUserByLoginName(username);
if (adminUser == null) {
throw new UsernameNotFoundException("不是新应用管理平台的用户!请联系管理员添加权限!");
}
//构建权限
Collection grantedAuthorities = new HashSet<>();
// 每位同学都默认拥有的角色
grantedAuthorities.add(new SimpleGrantedAuthority(SecurityConstant.ROLE_COMMON));
//查询用户角色
List roles = this.adminUserService.getUserRoles(adminUser.getId());
if (CollectionUtils.isNotEmpty(roles)) {
for (AdminUserRole ur : roles) {
GrantedAuthority ga = new SimpleGrantedAuthority(SecurityConstant.ROLE_NAME_PREFIX + ur.getRoleId());
grantedAuthorities.add(ga);
}
}
return new User(adminUser.getUserName(), StringUtils.EMPTY, grantedAuthorities);
}
public final class SimpleGrantedAuthority implements GrantedAuthority {
private static final long serialVersionUID = 420L;
private final String role;
public SimpleGrantedAuthority(String role) {
Assert.hasText(role, "A granted authority textual representation is required");
this.role = role;
}
public String getAuthority() {
return this.role;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else {
return obj instanceof SimpleGrantedAuthority ? this.role.equals(((SimpleGrantedAuthority)obj).role) : false;
}
}
public int hashCode() {
return this.role.hashCode();
}
public String toString() {
return this.role;
}
}
实现UserDetails接口定义好变量。
需要自定义实现类实现FilterInvocationSecurityMetadataSource接口。通过loadResourceDefine方法可以实现资源与权限的对应关系。
要使我们自定义的MFilterInvocationSecurityMetadataSource生效,我们还需要定义一个MyFilterSecurityInterceptor类。
这里的数据需要从数据库中取得。另外自定义接口UrlMatcher,实现类为AntUrlPathMatcher。
@Component
public class MFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
public IRescAndRoleService iRescAndRoleService ;
@Autowired
private IUserService iUserService ;
private UrlMatcher urlMatcher = new AntUrlPathMatcher();
// 资源权限集合
private static Map> resourceMap = null;
public void loadResourceDefine(){
resourceMap = new HashMap>();
//取得用户信息
List userList = iUserService.query();
//取得资源与角色列表
List resourceList = iRescAndRoleService.query();
System.out.println(resourceList);
for (RescAndRole resource : resourceList) {
Collection atts = new ArrayList();
atts.add(new SecurityConfig(resource.getRoleName() ));
resourceMap.put(resource.getResString(), atts);
}
}
@Override
public Collection getAttributes(Object o) throws IllegalArgumentException {
loadResourceDefine();//防止无法注入问题
// guess object is a URL.
String url = ((FilterInvocation) o).getRequestUrl();
Iterator ite = resourceMap.keySet().iterator();
while (ite.hasNext()) {
String resURL = ite.next();
if (urlMatcher.pathMatchesUrl(resURL, url)) {
return resourceMap.get(resURL);
}
}
return null;
}
@Override
public Collection getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class aClass) {
return true;
}
}
public class AntUrlPathMatcher implements UrlMatcher {
private boolean requiresLowerCaseUrl;
private PathMatcher pathMatcher;
public AntUrlPathMatcher() {
this(true);
}
public AntUrlPathMatcher(boolean requiresLowerCaseUrl) {
this.requiresLowerCaseUrl = true;
this.pathMatcher = new AntPathMatcher();
this.requiresLowerCaseUrl = requiresLowerCaseUrl;
}
public Object compile(String path) {
if (this.requiresLowerCaseUrl) {
return path.toLowerCase();
}
return path;
}
public void setRequiresLowerCaseUrl(boolean requiresLowerCaseUrl) {
this.requiresLowerCaseUrl = requiresLowerCaseUrl;
}
public boolean pathMatchesUrl(Object path, String url) {
if (("/**".equals(path)) || ("**".equals(path))) {
return true;
}
return this.pathMatcher.match((String) path, url);
}
public String getUniversalMatchPattern() {
return "/**";
}
public boolean requiresLowerCaseUrl() {
return this.requiresLowerCaseUrl;
}
public String toString() {
return super.getClass().getName() + "[requiresLowerCase='"
+ this.requiresLowerCaseUrl + "']";
}
}
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor
implements Filter {
private FilterInvocationSecurityMetadataSource securityMetadataSource;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public Class getSecureObjectClass() {
return FilterInvocation.class;
}
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);
}
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public void destroy() {
}
public void init(FilterConfig arg0) throws ServletException {
}
}
自定义一个决策管理器MyAccessDecisionManager实现AccessDecisionManager接口。其中的decide方法,决定某一个用户是否有权限访问某个url
/* (non-Javadoc)
* @see org.springframework.security.access.AccessDecisionManager#decide(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection)
* 该方法决定该权限是否有权限访问该资源,其实object就是一个资源的地址,authentication是当前用户的
* 对应权限,如果没登陆就为游客,登陆了就是该用户对应的权限
*/
@Override
public void decide(Authentication authentication, Object object,
Collection configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if(configAttributes == null) {
return;
}
System.out.println(object.toString()); // object is a URL.
//所请求的资源拥有的权限(一个资源对多个权限)
Iterator iterator = configAttributes.iterator();
while(iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
//访问所请求资源所需要的权限
String needPermission = configAttribute.getAttribute();
System.out.println("访问"+object.toString()+"需要的权限是:" + needPermission);
//用户所拥有的权限authentication
Collection authorities = authentication.getAuthorities();
for(GrantedAuthority ga : authorities) {
if(needPermission.equals(ga.getAuthority())) {
return;
}
}
}
//没有权限
throw new AccessDeniedException(" 没有权限访问! ");
}
@Override
public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean supports(Class clazz) {
// TODO Auto-generated method stub
return true;
}
添加Seucrity的过滤器,将拦截所有资源访问
只能配置成 /*
contextConfigLocation
WEB-INF/config/security.xml
WEB-INF/config/spring-mybatis.xml
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
参考:https://segmentfault.com/a/1190000010232638