一、前言
该文只是自己在学习微人事时,对Spring Security的初步了解。仅记录一下,供自己温习。
二、认证
spring security登录认证的流程----以用户名/密码验证为准
vhr这里的逻辑主要是:改写的主要是一个UserDetailService的loadUserByUsername(HrService)同时提供一个继承UsernamePasswordAuthenticationFilter并替代其工作的LoginFilter完成认证功能。LoginFilter额外添加的逻辑主要是对用户输入的验证码进行认证。
三、鉴权
鉴权则主要是橙色部分的这个FilterSecurityInterceptor。
大致流程:
进行鉴权时执行的是doFilter()方法。该方法源码只有两行,首先创建一个FilterInvocation对象,然后执行将FilterInvocation对象作为invoke方法参数,执行this.invoke()。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
this.invoke(fi);
}
invoke方法的源码如下:
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//该请求已经认证过了
if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
if (fi.getRequest() != null && this.observeOncePerRequest) {
fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
//鉴权
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
所以,调用了父类AbstractSecurityInterceptor的beforeInvocation()方法,该方法主要通过一个AbstractSecurityInterceptor得到一个Collection< ConfigAttribute>对象,该容器存储着配置文件中配置的过滤规则。然后通过authenticateIfRequired()得到一个Authentication对象,最后使用持有的AccessDecisionManager的dicide()方法去进行鉴权。
所以,最终的鉴权逻辑在AccessDecisionManager中完成。
示例:
Web应用的话,我们假设自定义一个CustomAccessDecisionManager类去实现AccessDecisionManager。
public class CustomAccessDecisionManager implements AccessDecisionManager {
//主要鉴权逻辑,authentication包含了当前请求用户的信息,包括权限信息,权限来自于登录认证时
//UserDetailsService中提供的authorities
//Object其实是FileterInvocation对象,可以得到request
//configAttributes为我们配置的过滤规则,即访问所需的权限
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
if (authentication == null) {
throw new AccessDeniedException("当前访问没有权限");
}
//需要的权限
ConfigAttribute configAttribute = iterator.next();
String needCode = configAttribute.getAttribute();
//用户所拥有的权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (StringUtils.equals(authority.getAuthority(), "ROLE_" + needCode)) {
return;
}
}
}
throw new AccessDeniedException("当前访问没有权限");
}
public boolean supports(ConfigAttribute attribute) {
return true;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
vhr鉴权部分主要是自定义:
- 实现了AccessDecisionManager接口的CustomUrlDecisionManager类
- 实现了FilterInvocationSecurityMetadataSource接口 (该接口用于加载访问时所需要的具体权限)的CustomFilterInvocationSecurityMetadataSource (简单情况下,我们直接在配置类中通过一些方法来设置一些鉴权规则,但是该项目中的权限信息是和Role表中的角色绑定的,即权限信息存储在数据库中。所以,需要自定义一个类来加载鉴权规则)
AccessDecisionManager是鉴权的主要逻辑所在:
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute : configAttributes) {
//获得访问所需要的角色
String needRole = configAttribute.getAttribute();
//如果是登录请求
if ("ROLE_LOGIN".equals(needRole)) {
if (authentication instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("尚未登录,请登录!");
}else {
//只要认证通过以后,鉴权部分直接通过,不需要任何权限
return;
}
}
//对于其他请求,将该请求所需要的角色与用户所拥有的角色进行挨个匹配
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足,请联系管理员!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
CustomFilterInvocationSecurityMetadataSource 用于从数据库中读取鉴权规则:
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
//menu表是存储ROLE所对应的权限
@Autowired
MenuService menuService;
//用来做urls字符串匹配
AntPathMatcher antPathMatcher = new AntPathMatcher();
//该方法返回本次访问需要的权限
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) object).getRequestUrl();
//获取所有角色所对应的菜单(menu)
List<Menu> menus = menuService.getAllMenusWithRole();
//匹配哪些菜单拥有该请求路径,即得到哪些角色可以访问该路径
for (Menu menu : menus) {
if (antPathMatcher.match(menu.getUrl(), requestUrl)) {
List<Role> roles = menu.getRoles();
String[] str = new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
str[i] = roles.get(i).getName();
}
return SecurityConfig.createList(str);
}
}
//如果该路径无法和任何一个menu所拥有的路径匹配,返回一个登录认证所需的角色
return SecurityConfig.createList("ROLE_LOGIN");
}
//返回所有定义的权限
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
//返回类对象是否支持校验
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}