一、权限系统E-R图
常用的权限管理系统中包括四个实体表,分别是用户表、角色表、权限表、资源表,以及他们之间的三个联系表,实体表之间都是多对多的关系
备注:写完了才发现角色表没用到,请忽略
二、SpringSecurity
2.1 主要组件
(1)SecurityContextHolder:主要作用是提供访问权限的SecurityContext
(2)SecurityContext:用于保存程序上下文安全相关信息
(3)Authentication:存储验证信息
(4)GrantedAuthority:授予用户访问权限
(5)UserDetails:应用程序的DAO,用于构建Authentication的关键信对象
(6)UserDetailsService:通过用户名或者唯一的字段来创建一个UserDetails对象
2.2 过滤器
本例中主要用到了UsernamePasswordAuthenticationFilter和FilterSecurityInterceptor
(1)UsernamePasswordAuthenticationFilter必须设置一个AuthenticationManager对象,由
AuthenticationManager对象的authenticate()方法来授权一个请求对象,授权失败则抛出AuthenticationException异常,然后通过SpringSecuriy ExceptionHandler 处理器处理,最后通过DispatchServlet返回给客户端授指定的资源
(2)FilterSecurityInterceptor包含两个重要对象,FilterInvocationSecurityMetadataSource和AccessDecisionManager,FilterInvocationSecurityMetadataSource这个对象加载所有资源,AccessDecisionManager这个主要对用户做资源的限制,对Request的处理
三、教程
3.1 SpringSecurityConfig,必须继承WebSecurityConfigurerAdapter ,@EnableWebSecurity注解启用SpringSecurity配置,@EnableGlobalMethodSecurity启用SpringSecurity全局配置,参数prePostEnabled=true 启用注解@PreAuthorize@ @PostAuthorize,securedEnabled=true启用注解@Secured,jsr250Enabled=true启用注解@PermitAll,@RolesAllowed等注解,都可以作为访问权限控制注解,区别在于@Secured可以使用表达式已经返回的集合做权限的控制;
@EnableWebSecurity
@Configuration
@Slf4j
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
@Resource//自定义UserDetailsService
private UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.securityInterceptor(filterSecurityInterceptor());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
//.accessDecisionManager(accessDecisionManager()) 此方法注册一个url的拦截器
.and()
.formLogin()//登陆拦截器
.loginPage("/index")//自定义登陆页面
.loginProcessingUrl("/login")//此处用的默认的处理登陆
.successForwardUrl("/loginSuccess")//登陆成功跳转页面,不要任何权限
.defaultSuccessUrl("/loginSuccess")//登陆成功跳转页面,有权的跳转到此页面
.failureUrl("/loginFailure")//登陆失败跳转页面
.permitAll()//权限配置
.and()
.logout().permitAll()
.and()
.csrf().disable()
.exceptionHandling().accessDeniedPage("/accessDenied")//权限拒绝url
;
}
@Bean//设置密码加密方式
public PasswordEncoder passwordEncoder(){
return new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return StringUtils.equals(rawPassword.toString(),encodedPassword);
}
};
}
@Bean
public AccessDecisionManager accessDecisionManager(){
return new AccessDecisionManager() {
@Override
public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(null== configAttributes || configAttributes.size() <=0) {//没有相关资源不做权限校验
return;
}
//遍历资源,如果拥有权限,安全校验通过
for (ConfigAttribute configAttribute : configAttributes){
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()){
if (StringUtils.equals(configAttribute.getAttribute(),grantedAuthority.getAuthority())){
return;
}
}
}
throw new AccessDeniedException("denied no right");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class> clazz) {
return true;
}
};
}
@Resource
private PermissionMapper permissionMapper;
@Resource
private ClResourceMapper resourceMapper;
@Bean
public FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource(){
//加载所有资源
HashMap> map =new HashMap<>();
List resources = resourceMapper.findAll();
for(ClResource resource : resources){
List configAttributes = new ArrayList<>();
List permissions = permissionMapper.findByResourceId(resource.getId());
for (Permission permission : permissions){
ConfigAttribute configAttribute = new SecurityConfig(permission.getName());
configAttributes.add(configAttribute);
}
map.put(resource.getPattern(),configAttributes);
}
return new FilterInvocationSecurityMetadataSource() {
@Override
public Collection getAttributes(Object object) throws IllegalArgumentException {
if (object instanceof FilterInvocation){
FilterInvocation fi = (FilterInvocation) object;
for (String pattern : map.keySet()){
AntPathRequestMatcher matcher = new AntPathRequestMatcher(pattern);
if (matcher.matches(fi.getHttpRequest())){
return map.get(pattern);//返回url匹配的资源
}
}
}
return null;
}
@Override
public Collection getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class> clazz) {
return true;
}
};
}
@Bean//配置FilterSecurityInterceptor
public FilterSecurityInterceptor filterSecurityInterceptor(){
FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
filterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
filterSecurityInterceptor.setSecurityMetadataSource(filterInvocationSecurityMetadataSource());
filterSecurityInterceptor.setObserveOncePerRequest(false);
return filterSecurityInterceptor;
}
3.2 UserService更具用户名加载用户及其权限
@Service
public class UserService extends InMemoryUserDetailsManager {
@Resource
private ClUserMapper clUserMapper;
@Resource
private PermissionMapper permissionMapper;
//加载用户和相关权限
@Override
public UserDetails loadUserByUsername(String username) {
ClUser clUser = clUserMapper.findByUsername(username);
if (clUser == null){
throw new UsernameNotFoundException("用户不存在");
}
List permissions = permissionMapper.findByAdminUserId(clUser.getId().intValue());
List authorities = new ArrayList<>();
if (permissions != null && permissions.size() > 0){
for (Permission permission : permissions){
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());
authorities.add(grantedAuthority);
}
}
return new User(clUser.getUsername(),clUser.getPassword(),authorities) ;
}
}
3.3 测试接口
@RestController
public class TestController {
@Resource
private PermissionMapper permissionMapper;
@GetMapping("api/allPermission")
public List allPermission(){
return permissionMapper.findAll();
}
@GetMapping("/hi/world")
public String hi(){
return "hello world";
}
@GetMapping("/test/test")
public String test(){
return "test";
}
@PreAuthorize("permitAll()")// 和下面 @PermitAll 作用一样
@PermitAll
@GetMapping("/study/study")
public String study(){
return "study";
}
@RolesAllowed("ADMIN")
// @PreAuthorize("hasRole('ADMIN')")
@GetMapping("/learn/learn")
public String learn(){
return "learn";
}
}
四 演示结果
查询userId=1的用户权限sql:
SELECTsys_user.username,sys_permission.name,sys_resource.patternFROMsys_userLEFTJOINsys_role_userONsys_user.id=sys_role_user.sys_user_idLEFTJOINsys_roleONsys_role_user.sys_role_id=sys_role.idLEFTJOINsys_permission_roleONsys_permission_role.role_id=sys_role.idLEFTJOINsys_permissionONsys_permission_role.permission_id=sys_permission.idLEFTJOINsys_permission_resourceONsys_permission_resource.permission_id=sys_permission.idLEFTJOINsys_resourceONsys_resource.id=sys_permission_resource.resource_idWHEREsys_user.id=1;
用户admin 是USER角色,访问的资源路径是api/**和/hi
登陆成功页面
请求/hi/world资源,是有权限的
请求test/**资源
对于没有权限的资源不允许访问
五 资料
1 官网文档:https://docs.spring.io/spring-security/site/docs/5.2.0.BUILD-SNAPSHOT/reference/htmlsingle/
2 参考博客:https://www.cnblogs.com/softidea/p/7068149.html
3 源码:https://github.com/NapWells/java_framework_learn/tree/master/springsecuritydemo