1.授权流程分析
授权一定是在认证通过之后,授权流程是通过FilterSecurityInterceptor拦截器来完成,FilterSecurityInterceptor通过调用SecuritymetadataSource来获取当前访问的资源所需要的权限,然后通过调用AccessDecisionManager投票决定当前用户是否有权限访问当前资源。授权流程如下
用户第一次访问是需要加载用户权限以及用户信息的,再访问成功后,在UserService中会返回当前登录成功的对象到SecurityContextHolder并保存,再用户下一次登录时会优先从SecurityContextHolder查询信息。
1.当客户端向某个资源发起请求,请求到达FilterSecurityInterceptor,然后会调用其父类AbstractSecurityInterceptor
的beforeInvocation方法做授权之前的准备工作
2.在beforeInvocation法中通过SecuritymetadataSource…getAttributes(object);获得资源所需要的访问权限 ,通过SecurityContextHolder.getContext().getAuthentication()获取当前认证用户的认证信息,即包含了认证信息和权限信息的Authentication对象
3.然后FilterSecurityInterceptor通过调用AccessDecisionManager.decide(authenticated, object, attributes);进行授权(authenticated中有用户的权限列表,attributes是资源需要的权限),该方法使用投票器投票来决定用户是否有资源访问权限
AccessDecisionManager接口有三个实现类,他们通过通过AccessDecisionVoter投票器完成投票,三种投票策略如下:
Affirmativebased : 只需有一个投票赞成即可通过
Consensusbased:需要大多数投票赞成即可通过,平票可以配置
Unanimousbased:需要所有的投票赞成才能通过
而投票器也有很多,如RoleVoter通过角色投票,如果ConfigAttribute是以“ROLE_”开头的,则将使用RoleVoter进行投票,AuthenticatedVoter 是用来区分匿名用户、通过Remember-Me认证的用户和完全认证的用户(登录后的)
4.投票通过,请求放行,响应对应的资源给客户端
2.Web授权
2.1.web授权API说明
在Security配置类中,可以通过HttpSecurity.authorizeRequests()给资源指定访问的权限,其API如下:
anyRequest():任何请求
antMatchers(“/path”) :匹配某个资源路径
authenticationed() : 保护URL需要登录访问
permitAll():指定url无需保护(放行)一般用户静态资源
hasRole(String role):某个资源需要用户拥有什么样的role才能访问
hasAuthority(String authority):某个资源需要用户拥有什么样的权限才能访问
hasAnyRole(String …roles):某个资源拥有指定角色中的一个就能访问
hasAnyAuthority(String … authorities):某个资源拥有指定权限中的一个就能访问
access(String attribute):该方法使用SPEL表达式,可以创建复杂的限制
hasIpAddress( String ip):拥有什么样的ip或子网可以访问该资源
这里是比较懒了,少创建一张用户角色表,正常创建应该是吧用户表里面的角色id拆分出来,有一张用户和角色关联表
用户表 这里用户密码是通过Security特有的PasswordEncoder加密方式加密,后面会有详细介绍
CREATE TABLE loginuser (
id int(10) NOT NULL AUTO_INCREMENT,
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
role_id int(10) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
菜单表 菜单表里面存有权限,这个是后面开启注解授权后权限
CREATE TABLE meu (
id int(5) NOT NULL AUTO_INCREMENT,
meuName varchar(255) DEFAULT NULL,
persim varchar(255) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
角色表
CREATE TABLE role (
id int(10) NOT NULL AUTO_INCREMENT,
roleName varchar(255) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
角色权限表
CREATE TABLE role_persim (
id int(5) NOT NULL AUTO_INCREMENT,
role_id int(5) NOT NULL,
meu_id int(5) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
这一个配置类就类似于Handlerinterceptor这一个配置类,起到拦截请求的作用。但在这里面有一个强大的密码解析器PasswordEncoder ,用BCrypt强哈希方法来加密,不可逆,此类每次进行加密的盐值均不相同,也就是说每次相同的明文加密后的密文均不相同,这样就能保证密码尽量小的被测出来。
package com.config;
import com.securityHandler.MyAuthenticationFailureHandler;
import com.securityHandler.MyAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.stereotype.Component;
@Component
@EnableWebSecurity
// 开启注解授权
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Bean
public PasswordEncoder passwordEncoder() {//密码密码解析器
//return NoOpPasswordEncoder.getInstance();
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/demo/login").permitAll()//登录请求放行
.antMatchers("/demo/goIndex").permitAll()//访问登录页面放行
.antMatchers("/index.html").permitAll() //对登录页面跳转路径放行
.anyRequest().authenticated() //其他路径都要拦截
.and().formLogin()
.passwordParameter("username")
.passwordParameter("password")//允许表单登录, 设置登陆页,验证登录账号密码
// .successForwardUrl("/demo/success") // 设置登陆成功后跳转的页面
.defaultSuccessUrl("/demo/success") // 默认登录成功访问指定页面,用户未登入,跳转至登入页面,如果登入成功,跳转至用户访问指定页面,用户访问登入页面,默认的跳转页面
.loginPage("/index.html") //登录页面跳转地址,如果用户没有登录,会跳转到登录页面
.loginProcessingUrl("/demo/login")//登录处理地址(必须)和登录表单请求地址一致,对应后用户登录进入认证流程,赋上对应得权限
//推出登录
.and().logout().permitAll()
.logoutUrl("/demo/logout")
.logoutSuccessUrl("/demo/goIndex")//和退出登录请求一致
// .deletecookies("JSESSIONID")
//退出登录
.invalidateHttpSession(true); //登出后session无效;*/
}
}
UserDetailsService 用户校验以及权限校验
package com.service.impl;
import com.entity.LoginUser;
import com.entity.Meu;
import com.service.ILoginUserService;
import com.service.IMeuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.List;
import java.util.stream.Collectors;
@Configuration
public class MyUserDetailService implements UserDetailsService {
@Autowired
private ILoginUserService loginUserService;
@Autowired
private IMeuService meuService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LoginUser loginUser = loginUserService.findByUsername(username);
if (loginUser == null){
throw new RuntimeException("此用户不存在,请检查输入的账号是否正确");
}
//查询该用户权限
List permission = meuService.selectPermission(loginUser.getId());
List authorities = permission.stream().map(p -> {
return new SimpleGrantedAuthority(p.getPersim());
}).collect(Collectors.toList());
//将对象用户密码权限信息先放到holder中返回USER对象
User user1 = new User(username,loginUser.getPassword(),authorities);
return user1;
}
}
HTML
//登录页面
登陆
登陆
//登录成功页面以及退出页面
登录成功
认证流程1.用户首次登录,表单请求与config中配置得登录请求路径一致
2.进入权限认证,校验用户信息,最后返回用户信息以及权限信息,封装再SecurityHolder中,用于下次用户访问资源校验权限
3.用户认证成功后,Security自带的缓存机制将用户信息以及权限封装并返回到浏览器
4.再一次访问资源,会在请求头携带之前返回的信息,通过改信息中的权限信息与holder中的权限信息比较,是否允许该用户访问资源
5.用户退出登录后,浏览器的cookie会失效,再次访问资源会因为没有权限跳转到登录页面。
到这里SpringBoot集成Security权限认证就算完结了,调用
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();//获取当前用户登录信息权限信息,这是从holder中获取,希望这文章能给大家一些帮助



