SpringSecurity学习第四篇 授权
日期: 2020-05-24 分类: 跨站数据测试 191次阅读
授权
什么是授权嗯?这里的授权不是说赋予你什么权限,而是说当你认证通过之后,你访问一个受保护的资源时,看你是否有这个权限,比如说对用户的操作,只可以管理员来执行,如果这个人是一个普通人就无法操作这些接口
授权流程
我们来看一下SpringSecurity中的授权流程。
1.当已认证的用户访问受保护的资源时,会被过滤器链(SecurityFilterChain) 中的FilterSecurityInterceptor
的子类拦截
2. FilterSecurityInterceptor会从SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource获取当前访问的资源需要哪些角色(Collection< ConfigAttribute>)
3. 最后FilterSecurityInterceptor会调用AccessDecisionManager进行授权决策,看当前用户是否拥有操作这个资源所拥有的角色,若决策通过,则可以访问该接口
下面是AccessDecisionManager接口,decide方法就是定义你的授权逻辑,我们一般在这里面来判断当前用户是否拥有这个资源所需要的角色。 decide有三个参数,第一个Authentication:就是认证之后的身份
Object对象这里是要访问的受保护资源,他是一个FilterInvocation类型,你可以通过这个对象获取当前所访问的路径。Collection集合就是要操作当前资源,所需要的角色。
这里说一下:其实我们说资源,你可以认为他就是一个接口,
public interface AccessDecisionManager {
// 实现这个方法,定义决策逻辑
void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;
boolean supports(ConfigAttribute var1);
boolean supports(Class<?> var1);
}
接下来上代码演示:
在上节的基础上,我们在增加两张表,分别是资源表,和资源角色关联表。这里我们权限是这样管理起来的。将操作资源的权限分配个角色,然后将角色很配给用户。一个资源可以被多个角色操作,一个人可以拥有多个不同的角色。当然了一个角色也是可以分给多个人的。这都是多对多的关系。
其实这里的权限,我们没有赋予它真实的含义,我们可以认为权限是一个动词。我们可以理解为权限是看用户是否拥有操作这个资源的能力的`操作
有这样五张表,menu就是定义系统中的资源,就是一些url规则,
上代码:
先看我们三个实体类
User类: 实现UserDetails
package org.javaboy.securitysql.bean;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author wj
* @version 1.0
* @date 2020/5/23 15:31
*/
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private boolean isEnabled;
private List<Role> roles;
/**
* 用户所拥有的角色,这里需要将用户的角色写入这个集合中,
* 最后可以通过Authentication获得到
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
for (Role role:roles) {
/**
* public SimpleGrantedAuthority(String role) {
* Assert.hasText(role, "A granted authority textual representation is required");
* this.role = role;
* }
*/
simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
}
return simpleGrantedAuthorities;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public void setEnabled(boolean enabled) {
isEnabled = enabled;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", isEnabled=" + isEnabled +
", roles=" + roles +
'}';
}
}
Role: 角色类,只有三个字段,name角色名称,nameZh角色中文名称。注意一点,在SpringSecurity中角色要以大写的ROLE_开头,否则出错哦,下边是role表
package org.javaboy.securitysql.bean;
/**
* @author wj
* @version 1.0
* @date 2020/5/23 15:52
*/
public class Role {
private Integer id;
private String name;
private String nameZh;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameZh() {
return nameZh;
}
public void setNameZh(String nameZh) {
this.nameZh = nameZh;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", name='" + name + '\'' +
", nameZh='" + nameZh + '\'' +
'}';
}
}
Menu:资源类,pattern是ant格式的匹配规则,这里面有一个List是看这个资源需要哪些角色才可以操作他
package org.javaboy.securitysql.bean;
import java.util.List;
/**
* @author wj
* @version 1.0
* @date 2020/5/23 17:16
*/
public class Menu {
private Integer id;
private String pattern;
private List<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "menu{" +
"id=" + id +
", pattern='" + pattern + '\'' +
", roles=" + roles +
'}';
}
}
接下来我们看一下UserService类,负责查询用户信息,loadUserByUsername是根据用户名查身份,
getRoles,根据用户id查到当前用户拥有哪些角色。sql的代码就不展示了
package org.javaboy.securitysql.service;
import org.javaboy.securitysql.bean.Role;
import org.javaboy.securitysql.bean.User;
import org.javaboy.securitysql.dao.UserMapeer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author wj
* @version 1.0
* @date 2020/5/23 15:37
*/
@Service
public class UserSerivce implements UserDetailsService {
@Autowired
UserMapeer userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(s);
if(user ==null){
return null;
}
List<Role> roles = userMapper.getRoles(user.getId());
user.setRoles(roles);
return user;
}
}
关键点
此时我们需要定义两个最关键的类,一个类就是获取当前访问资源所需要的角色(FilterInvocationSecurityMetadataSource类型),一个类是决策类,看用户是否具有和这个角色,来进行访问控制(AccessDecisionManager)
首先我们创建一个FilterSecurityInvocationSecurityMetaDataSource的实现类,我们们通过这样获取当前访问路径拥有的角色的。我们通过ant匹配器,我们在数据库配置的是/user/*这样的路径,当我们访问/user/select的时候,那么他就匹配到了/user/ *, 此时先将所有的资源和他相应的角色查出来。然后将当前路径匹配到的ant资源对应的角色赋值到集合中去。
package org.javaboy.securitysql.filter;
import org.javaboy.securitysql.bean.Menu;
import org.javaboy.securitysql.bean.Role;
import org.javaboy.securitysql.dao.MenuMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
import java.util.List;
/**
* @author wj
* @version 1.0
* @date 2020/5/23 17:20
*/
@Component
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
// ant路径格式匹配器
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
MenuMapper menuMapper;
/**
* 当前访问资源所需要的角色
* @param object
* @return
* @throws IllegalArgumentException
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// HttpServletRequest request = ((FilterInvocation) object).getRequest();
// 访问的资源路径,
String requestUrl = ((FilterInvocation) object).getRequestUrl();
System.out.println(requestUrl);
// 查询到所有的角色
List<Menu> menus = menuMapper.selectAllMenu();
// 通过ant匹配,查看当前路径所需要的角色
for (Menu menu:menus){
if(antPathMatcher.match(menu.getPattern(),requestUrl)){
List<Role> roles = menu.getRoles();
String[] roleStr = new String[roles.size()];
for (int i = 0; i < roleStr.length; i++) {
roleStr[i]=roles.get(i).getName();
}
// 返回当前路径所需要的角色
return SecurityConfig.createList(roleStr);
}
}
// 如果没有匹配的话,让他登录即可访问,随便授予一个角色,不要给null,防止在别的地方空指针
return SecurityConfig.createList("ROLE_login");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
然后定义决策管理器,实现AccessDecisionManager类,decide的三个参数就是上面解释的那样,我们在这个方法中判断该用户使用有和这个资源所需要的角色
package org.javaboy.securitysql.filter;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
* @author wj
* @version 1.0
* @date 2020/5/23 18:00
*/
@Component
public class MyAcccessDecisionManager implements AccessDecisionManager {
/**
*
* @param authentication 当前认证的用户
* @param o
* @param collection 当前访问资源所需要的角色
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
// 当前用户所具有的角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (ConfigAttribute configAttribute : collection) {
// 已经约定好,直接放行,登录即可
if(configAttribute.getAttribute().equals("ROLE_login")){
return;
}
for (GrantedAuthority authority : authorities) {
if(authority.getAuthority().equals(configAttribute.getAttribute())){
return;
}
}
}
// 如果有权限的话,就不会走到这,直接抛出异常
throw new AccessDeniedException("非法请求,没有权限");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
最后一步就是我们的配置类了,这里主要是将我们定义的两个类设置进去】
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(acccessDecisionManager);
return o;
}
})
package org.javaboy.securitysql.config;
import org.javaboy.securitysql.filter.MyAcccessDecisionManager;
import org.javaboy.securitysql.filter.MyFilterInvocationSecurityMetadataSource;
import org.javaboy.securitysql.service.UserSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
* @author wj
* @version 1.0
* @date 2020/5/23 18:10
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserSerivce userSerivce;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSerivce);
}
@Autowired
MyAcccessDecisionManager acccessDecisionManager;
@Autowired
MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
// 安全配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(acccessDecisionManager);
return o;
}
})
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}
}
总结: 授权操作主要是FilterSecurityIntercepor在工作。
除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog
精华推荐