写在前面
开辟了一个专门板块。把项目中遇到过的技术点做了一个整理,形成自己的技术栈。在码云
上也建了一个仓库,存放这些代码。码云地址https://gitee.com/lth1024/SbMuster。欢迎各位大佬们PullRequest~。这期是SpringBoot整合Security框架。Ps:本来是想先学习Shiro的,但手上项目中用的是Security框架,所以先学习Security了后面再介绍Shiro。
推荐学习地址: http://blog.itwolfed.com/blog/14
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
起初
只要加入依赖,项目的所有接口都会被自动保护起来。
加入上面的jar包后。在地址栏中输入localhost:8080/index,你会发现自动跳转到了一个登陆界面(login.html),我们完全没有写过登陆界面,所以这个是springsecurity自带的一个登录页,登陆的用户名为user,密码是输出在console中的uuid字符串。
在我们配置Security之前,它默认拦截所有页面并会自动生成一个登陆的账号密码,但这显然不是我们想要的样子。下面我们对它进行改造。
简介
Spring Security,这是一种基于Spring AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。
http://blog.itwolfed.com/blog/14链接参考。下面的图很棒。学习学习~
自己在学习搭建过程中把上述步骤进行了简化。
MyAccessDecisionManager的decide()校验
Do it
1.Web服务器启动
可以自定义拦截所需要的页面,也可设置默认放行的页面。项目中配置了Swagger,可以在permitAll()中进行放行。
2.加载过滤链条
3.加载【我的过滤器安全拦截器】
MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter
每种受支持的安全对象类型(方法调用或Web请求)都有自己的拦截器类,它是AbstractSecurityInterceptor的子类。
AbstractSecurityInterceptor是一个实现了对受保护对象的访问进行拦截的抽象类。
【我们自定义MyFilterSecurityInterceptor就是想使用我们之前自定义的 AccessDecisionManager和securityMetadataSource。】
@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
//筛选 调用 安全 元数据源
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
//我的访问决策管理器
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
}
AbstractSecurityInterceptor中的方法说明:
1.beforeInvocation()方法实现了对访问受保护对象的权限校验,内部用到了AccessDecisionManager和AuthenticationManager;
2.finallyInvocation()方法用于实现受保护对象请求完毕后的一些清理工作,主要是如果在beforeInvocation()中改变了SecurityContext,则在finallyInvocation()中需要将其恢复为原来的SecurityContext,该方法的调用应当包含在子类请求受保护资源时的finally语句块中。
3.afterInvocation()方法实现了对返回结果的处理,在注入了AfterInvocationManager的情况下默认会调用其decide()方法。
4.用户登录【AuthenticationManager认证管理器进行响应】
5.通过用户输入的用户名和密码,然后再根据用户定义的密码算法和盐值等进行计算并和数据库比对。
验证成功
MyUserDetailsService进行响应。
6.根据用户名从数据库中提取该用户的权限列表,组合成UserDetails供Spring Security使用。
*
/**
* @description: GrantedAuthority授予权限
* 1.GrantedAuthority是通过AuthenticationManager设置到Authentication对象中的
* 2.所有的Authentication实现类都保存了一个GrantedAuthority列表,其表示用户所具有的权限。
* 3.然后AccessDecisionManager将从Authentication中获取用户所具有的GrantedAuthority来鉴定用户是否具有访问对应资源的权限。
*/
@Data
public class Role implements GrantedAuthority{
private Long id;
private String name;
/**
* 权限点可以为任何字符串,不一定非要用角色名。
*/
@Override
public String getAuthority() {
return name;
}
}
7.用户点击某个功能(URL)触发MyAccessDecisionManager类的decide()
8.通过decide方法对用户的资源访问进行拦截(URL)
那么这个URL就跟MyInvocationSecurityMetadataSourceService里HashMap
[权限 对应的所属者]对比。若两者相同,则根据该url提取出Map结构的数据中的value来,这说明,若要请求这个URL,必须具有跟这个URL相对应的权限值(角色)。这个权限有可能是一个单独的权限,也有可能是一个权限列表,也就是说,一个URL有可能被多种权限(角色)访问。
/**
* 通过传递的参数来决定用户是否有访问对应受保护对象的权限
* @param authentication 包含了当前的用户信息,包括拥有的权限。这里的权限来源就是前面登录时UserDetailsService中设置的authorities。
* @param o 就是FilterInvocation对象,可以得到request等web资源
* @param collection collection是本次访问需要的权限
* 增:{角色1,角色2,角色3} 删:{角色1,角色4,角色5} 改:{角色1,角色2}......
*/
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
if(null == collection || collection.size()<=0){
return;
}else {
String needRole;
Iterator<ConfigAttribute> iterator = collection.iterator();
while (iterator.hasNext()){
ConfigAttribute configAttribute = iterator.next();
needRole = configAttribute.getAttribute();
/**
//根据用户id查找用户对应的角色集合
List<Role> roles = roleMapper.getRolesByUserId(user.getId());
user.setAuthorities(roles);
*/
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
if(needRole.trim().equals(grantedAuthority.getAuthority())){
return;
}
}
}
throw new AccessDeniedException("当前访问没有权限");
}
}
9. MyInvocationSecurityMetadataSourceService。用是用来【储存请求与权限的对应关系】
/**
* 每一个权限对应其所属者的角色 Collection<ConfigAttribute>决策器会用到
* 增:{角色1,角色2,角色3} 删:{角色1,角色4,角色5} 改:{角色1,角色2}......
* String:权限
* Collection<ConfigAttribute>:角色集合
*/
private static HashMap<String,Collection<ConfigAttribute>> map = null;
/**
* 初始化 [权限 对应的所属者]
select t1.name as name,t3.url as url
from role t1,role_permission t2,permission t3
where (t1.id=t2.role_id and t2.permission_id=t3.id)
*/
public void loadResourceDefine() {
map = new HashMap<>(16);
//初始化 [权限 对应的所有角色] 例如:增:{角色1,角色2,角色3} 删:{角色1,角色4,角色5} 改:{角色1,角色2}......
List<Permission> rolePermissons = permissionMapper.getRolePermissions();
//某个资源 可以被哪些角色访问
for (Permission rolePermisson : rolePermissons) {
//权限
String url = rolePermisson.getUrl();
//角色
String roleName = rolePermisson.getName();
//配置属性
ConfigAttribute role = new SecurityConfig(roleName);
//例如:增:{角色1,角色2,角色3} 删:{角色1,角色4,角色5} 改:{角色1,角色2}......
/**
* 进行校验:原有权限添加新的角色 or 新增了权限添加相应角色
*/
if(map.containsKey(url)){
map.get(url).add(role);
}else{//有新的权限的话 也要把对应所属者的角色 添加到map字典中
List<ConfigAttribute> list = new ArrayList<>();
list.add(role);
map.put(url,list);
}
}
}