Spring Security
介绍
1、简介
Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。
正如你可能知道的关于安全方面的两个核心功能是“认证”和“授权”,一般来说,Web 应用的安全性包括**用户认证(Authentication)和用户授权(Authorization)**两个部分,这两点也是 SpringSecurity 重要核心功能。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。
通俗点说就是系统认为用户是否能登录
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
通俗点讲就是系统判断用户是否有权限去做某些事情。
2、同款产品对比
3.1、Spring Security
Spring 技术栈的组成部分。
https://spring.io/projects/spring-security
通过提供完整可扩展的认证和授权支持保护你的应用程序。
SpringSecurity 特点:
⚫ 和 Spring 无缝整合。
⚫ 全面的权限控制。
⚫ 专门为 Web 开发而设计。
◼旧版本不能脱离 Web 环境使用。
◼新版本对整个框架进行了分层抽取,分成核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。
⚫ 重量级。
3.2、 Shiro
Apache 旗下的轻量级权限控制框架。
特点:
⚫ 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
⚫ 通用性。
◼好处:不局限于 Web 环境,可以脱离 Web 环境使用。
◼缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。
Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。
相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。
要对Web资源进行保护,最好的办法莫过于Filter
要想对方法调用进行保护,最好的办法莫过于AOP。
Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。
如图所示,一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sWVLXfr6-1680514805016)(C:\Users\石文学\OneDrive\图片\屏幕快照\image-20230206104938580.png)]
这里面我们只需要重点关注两个过滤器即可:UsernamePasswordAuthenticationFilter
负责登录认证,FilterSecurityInterceptor
负责权限授权。
说明:Spring Security的核心逻辑全在这一套过滤器中,过滤器里会调用各种组件完成功能,掌握了这些过滤器和组件你就掌握了Spring Security!这个框架的使用方式就是对这些过滤器和组件进行扩展。
一、学习目标:
1.1、简单了解
- 了解Spring Security的安全管理功能
- 了解Spring Security的安全配置
- 掌握Spring Security的用户认证
- 掌握Spring Security的权限管理
- Spring Security控制前端页面实现
二、快速入门
2.1、主要概念:
- 认证:Authentication
- 授权:Authenrization
2.2、基础配置
1、导入项目
2、导入Spring Security依赖
3、配置Spring Security安全管理
2.3、搭建安全管理测试环境
1、只要已导入Spring Security相关依赖:Spring就提供了一个Spring Security的启动器。
一旦引入了Spring Security的启动器,Spring Security、WebFlux的相关的安全功能部分已经生效。
2、Spring Security的配置。
3、启动项目后会在控制台输出Spring Security登录密码。
4、访问localhost:8080地址的时候,安全框架自动拦截了目标访问的资源,直接跳转到http://localhost:8080/login页面进行用户登录认证。
5、默认情况下,安全框架生成的登录页面,默认值为
用户名:user
密码:项目启动时控制台输出的密码
2.4、MVC Security安全配置介绍
使用Spring boot和Spring MVC进行项目开发时,如果项目引入spring-boot-starter-security依赖启动器,MVC Security安全管理功能就会自动生效,其默认的安全配置是在SecutiryAutoConfiguration和UserDetailsSeviceAutoConfiguration中实现的。其中,SecutiryAutoConfiguration会导入并自动化配置SpringBootWebSecurityConfiguration用于启动Web安全管理,UserDetailsSeviceAutoConfiguration则用于配置用户身份信息。
通过自定义WebSecurityConfigurerAdapter类型的Bean组件,可以完全关闭Security提供的Web应用默认安全配置,但是不会关闭UserDetailsService用户信息自动配置类。如果要关闭UserDetailsService默认用户信息配置,
可以自定义UserDetailsService、AuthenticationProvider或AuthenticationManager类型的Bean组件。另外,可以通过自定义WebSecurityConfigurerAdapter类型的Bean组件覆盖默认访问规则。Spring Boot提供了非常多方便的方法,可用于覆盖请求映射和静态资源的访问规则。
自定义安全管理模块:
- WebSecurityConfigurerAdapter用来管理安全框架的配置,如果开发者自定义安全管理,需要实现这个接口。自定义一个类,然后实现接口或者其他类,配置类由@Configration注解修饰。
- 实现抽象方法,或者重写父类或者父接口中的部分方法。
方法 | 描述 |
---|---|
configure(AuthenticationManagerBuilder auth) | 定制用户认证管理器来实现用户认证(认证) |
configre(HttpSecurity http) | 定制基于HTTP请求的用户访问控制(授权) |
三、认证及授权
WebSecurityConfigurerAdapter提供了5中自定义认证。使用了configure(AuthenticationManagerBuilder auth)方法来完成用户的自定义认证。
自定义用户认证方式分类见下:学习前三种认证方式。
- 内存身份认证
- JDBC身份认证
- 身份详情服务认证(这里主讲)
- LDAP身份认证(了解)
- 身份认证提供商(了解)
我在这里主要学习了身份详情服务认证
3.1、认证管理
1、前期准备
假设我们已经准备了有实体类Customer、Authority;CustomerMapper、CustomerService、CustomerServiceImpl
2、UserDetailsService身份认证使用
UserDetailsService是Spring安全框架提供的一个接口。用来封装用户的自定义权限信息(Customer、Authority)
UserDetailsService是一个接口,它有一个loadUserByUsername(String username)抽象方法,通过指定用户名来加载对应的用户信息,还可以进行权限的校验。
具体使用:因为它是Spring官方提供的一个接口,所以我们的任务是编写一个实现类UserDetailsServiceImpl自定义实现loadUserByUsername(String username)抽象方法,通过UserDetailsServiceImpl编写自定义的认证规则。
UserDetailsServiceImpl实现类如下:
@Service
public class UserDetailsServiceImpl implements UserDetailsService{
//权限认证层依赖于用户模块的业务层
@Autowired
private CustomerService customerService;
//会被自动回调,会将登录表单中用户的名称传递给该方法
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
//通过业务层调用,查询用户信息
Customer customer = customerService.getCustomer(username);
//通过业务层调用,获取指定用户的权限信息
List<Authority> authorityes = customerService.getCustomerAuthority(username);
//我们自己获取到的authorityes权限信息不能直接使用,需要封装成Spring官方指定的SimpleGrantedAuthority类,我们通过stream流实现authorityes封装,源码分析如下
List<SimpleGrantedAuthority> authorityList = authorityes.stream.map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList());
//UserDetails对象表示封装了:当前的用户以及当前用户的权限信息,返回给安全框架。
//所以我们还需进一步将我们自定义的用户对象以及权限信息封装成Spring官方提供的User类型,user()源码分析如下:
if(customer != null){
UserDetails userDetails = new User(customer.getUsername(),customer.getPassword(),authorityList);
return userDetails;
}else{
throw new UsernameNotFoundException("此用户不存在");
}
}
}
SimpleGrantedAuthority源码分析:
public final class SimpleGrantedAuthority implements GrantedAuthority{
//1.序列化ID:通过序列化的ID值来唯一的标识一个对象
//2.序列化ID:反序列化需要使用
private static final long serialVersionUID = 570L;
//权限:期望接收一个权限的字符串的取值
private final String role;
//....
}
User()实现了UserDetails,源码如下:
//Spring安全框架提供的user实体类
public class User implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = 530L;
private static final Log logger = LogFactory.getLog(User.class);
//主要关注一下这三属性,我们就是将我们的自定义对象及权限封装到User这个对象的这三属性
private String password;
private final String username;
//权限
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
public User(String username, String password, Collection<? extends GrantedAuthority> authorities{
this(username, password, true, true, true, true, authorities);
}
}
以上是认证的第一步,第二步回到配置类SecurityConfig中
//@EnableWebSecurity是开启SpringSecurity的默认行为
@EnableWebSecurity
//开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认
@EnableGlobalMethodSecurity(prePostEnabled = true)
//继承WebSecurityConfigurerAdapter
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//声明一个UserDetailsService对象
@Autowired
private UserDetailsService userDetailsService;
/**
自定义用户认证的管理
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
//注意必须构建一个密码的编码器
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//设置认证方式为UserDetailsService的方式
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
}
}
以上只是认证功能的实现,授权如下:
3.2、授权管理
3.2.1、授权分析
1、重写WebSecurityConfigurerAdapter类中的configure(HttpSecurity http)方法。此方法是基于HTTP请求的协议来进行权限控制(就是判断当前的请求地址是否可以被放行处理).
方法 | 描述 |
---|---|
authorizeRequests() | 开启基于HttpServletRequest请求访问的限制 |
formLogin() | 开启基于表单的用户登录 |
httpBasic() | 开启基于HTTP请求的Basic认证登录 |
logout() | 开启退出登录的支持 |
sessionManagerment() | 开启Session管理配置 |
rememberMe() | 开启记住我功能 |
csrf() | 开启CSRF跨域请求伪造防护功能 |
授权功能实现如下:回到配置类中SecurityConfig,添加如下方法即可。
@Override
protected void configure(HttpSecurity http) throws Exception {
// 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
http
//关闭csrf跨站请求伪造
.csrf().disable()
// 开启跨域以便前端调用接口
.cors().and()
.authorizeRequests()
.antMatchers("/login/**").permtitAll()//放行静态资源下
// 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的
.antMatchers("/admin/system/index/login").permitAll()
// 这里意思是其它所有接口需要认证才能访问
.anyRequest().authenticated()
.and()
//TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。
.addFilterBefore(new TokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class)
.addFilter(new TokenLoginFilter(authenticationManager(),redisTemplate));
//禁用session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
/**
* 配置哪些请求不拦截
* 排除swagger相关请求
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/admin/modeler/**","/diagram-viewer/**","/editor-app/**","/*.html","/admin/processImage/**","/admin/wechat/authorize","/admin/wechat/userInfo","/admin/wechat/bindPhone","/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
}
3.3、自定义登录页面控制
1、配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//基于表单的用户登录验证开启
.loginPage("/userLogin").permitAll()//放行当前请求;默认情况下,表单中的用户username、密码password,如果按照默认规则编写表单,会将参数自动传递给后台安全框架
.usernameParameter("name")
.passwordParameter("pwd")
.defaultSuccessUrl("/")//如果用户和密码验证成功,则打开那一个页面
.failureUrl("/userLogin?error");//如果登录的用户或密码输出错误,则打开登录页面
}
2、注意:
要在授权管理中放行login下的静态资源
.antMatchers("/login/**").permitAll()
3.4、自定义退出登录
配置
http.logout()//表示自定义用户的退出功能
.logoutUrl("/mylogout")//表示什么样的url请求会触发退出登录
.logoutSuccessUrl("/userLogin");//表示如果用户退出登录操作成功,跳转到指定的页面(login.html页面)
四、结合JWT+Redis
4.1、思路分析
登录
①自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt
把用户信息存入redis中
②自定义UserDetailsService
在这个实现类中去查询数据库
校验:
①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder
4.2、引入
根据以上入门的学习,我们需要去继承WebSecurityConfigurerAdapter写一个自己的配置类;需要去实现UserDetailsService自定义自己的loadUser方法,重写loadUser方法,在封装权限时需要封装成SimpleGrantedAuthority对象;最后还需封装成UserDetails对象返回。以上我们是通过Spring Security官方提供的User()对象返回。
但是实际中我们一般是数据库中具体的用户实体类比如Customer类,我们让Customer去继承User()类
public class CustomUser extends User {
/**
* 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。(这里我就不写get/set方法了)
*/
private SysUser sysUser;
public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
super(sysUser.getUsername(), sysUser.getPassword(), authorities);
this.sysUser = sysUser;
}
public SysUser getSysUser() {
return sysUser;
}
public void setSysUser(SysUser sysUser) {
this.sysUser = sysUser;
}
}
loadUser方法的返回值也会有所改变:直接返回Customer对象,因为它已经继承User(),而User()实现了UserDetails,所以返回的就是Userdetails对象。
/**
* 根据用户名获取用户对象
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysMenuService sysMenuService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = sysUserService.getByUsername(username);
if(sysUser == null){
throw new UsernameNotFoundException("用户名不存在!");
}
if(sysUser.getStatus().intValue() == 0) {
throw new RuntimeException("账号已停用");
}
//根据userId查询用户操作权限数据
List<String> userPermsList = sysMenuService.findUserPermsListByUserId(sysUser.getId());
//创建list集合,封装最终权限数据
List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
//查询list集合遍历
for (String perm : userPermsList) {
authorityList.add(new SimpleGrantedAuthority(perm.trim()));
}
return new CustomUser(sysUser, authorityList);
}
}
4.3、自定义认证管理
首先,我们需要自定义登录接口LoginUserServiceImpl,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。,怎么放行呢?去配置类里面放行。
//@EnableWebSecurity是开启SpringSecurity的默认行为
@EnableWebSecurity
//开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认
@EnableGlobalMethodSecurity(prePostEnabled = true)
//继承WebSecurityConfigurerAdapter
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//声明一个UserDetailsService对象
@Autowired
private UserDetailsService userDetailsService;
/**
自定义用户认证的管理
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
//注意必须构建一个密码的编码器
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//设置认证方式为UserDetailsService的方式
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
}
}
在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。怎么注入呢?回到配置类中固定这样写就ok!
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
以上就是简单配置,接下来就是去实现登录接口,生成token。Go!
@Service
public class LoginServiceImpl implements LoginServcie {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Override
public ResponseResult login(User user) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if(Objects.isNull(authenticate)){
throw new RuntimeException("用户名或密码错误");
}
//使用userid生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
//authenticate存入redis
redisCache.setCacheObject("login:"+userId,loginUser);
//把token响应给前端
HashMap<String,String> map = new HashMap<>();
map.put("token",jwt);
return new ResponseResult(200,"登陆成功",map);
}
}
以上就是生成token的过程,是不是很简单啊!
接下来我们要做的就是自定义认证过滤器
4.4、认证过滤器
首先,我们需要自定义一个过滤器类,然后继承OncePerRequestFilter。什么是认证过滤器呢?因为前面我们已经通过UserId生成了token,现在我们要做的就是从token里面解析出UserId,
使用userid去redis中获取对应的LoginUser对象。因为不能直接使用LoginUser对象存入SecurityContextHolder,
需要封装成UsernamePasswordAuthenticationToken对象,此对象实现了Authentication,最后将Authentication对象存入SecurityContextHolder
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
//放行
filterChain.doFilter(request, response);
return;
}
//解析token
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey = "login:" + userid;
LoginUser loginUser = redisCache.getCacheObject(redisKey);
if(Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//存入SecurityContextHolder
//TODO 获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
你以为到这里就结束了吗?还有最后一步,还是回到配置类中,在configure中添加过滤器链
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//把token校验过滤器添加到过滤器链中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
4.5、退出登录
我们只需要定义一个退出方法,然后获取SecurityContextHolder中的认证信息,删除redis中对应的数据即可。
@Service
public class LoginServiceImpl implements LoginServcie {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Override
public ResponseResult logout() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userid = loginUser.getUser().getId();
redisCache.deleteObject("login:"+userid);
return new ResponseResult(200,"退出成功");
}
}
以上就是我们的全部认证过程,继续往下我们的授权过程吧!
4.6、自定义授权管理
4.6.1 授权基本流程
在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。
所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。
然后设置我们的资源所需要的权限即可。
4.6.2 授权实现
4.6.2.1 限制访问资源所需权限
SpringSecurity为我们提供了基于注解的权限控制方案,这也是我们项目中主要采用的方式。我们可以使用注解去指定访问对应的资源所需的权限。
但是要使用它我们需要先开启相关配置。在配置类头上加注解即可
@EnableGlobalMethodSecurity(prePostEnabled = true)
然后就可以使用对应的注解。@PreAuthorize
@RestController
public class HelloController {
@RequestMapping("/hello")
@PreAuthorize("hasAuthority('test')")
public String hello(){
return "hello";
}
}
4.6.3 封装权限信息
我们前面在写UserDetailsServiceImpl的时候说过,需要从数据库中查询出用户对应的权限信息封装成SimpleGrantedAuthority对象,最后封装到UserDetails中返回。
@Service
public class UserDetailsServiceImpl implements UserDetailsService{
//权限认证层依赖于用户模块的业务层
@Autowired
private CustomerService customerService;
//会被自动回调,会将登录表单中用户的名称传递给该方法
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
//通过业务层调用,查询用户信息
Customer customer = customerService.getCustomer(username);
//通过业务层调用,获取指定用户的权限信息
List<Authority> authorityes = customerService.getCustomerAuthority(username);
//我们自己获取到的authorityes权限信息不能直接使用,需要封装成Spring官方指定的SimpleGrantedAuthority类,我们通过stream流实现authorityes封装,源码分析如下
List<SimpleGrantedAuthority> authorityList = authorityes.stream.map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList());
//UserDetails对象表示封装了:当前的用户以及当前用户的权限信息,返回给安全框架。
//所以我们还需进一步将我们自定义的用户对象以及权限信息封装成Spring官方提供的User类型,user()源码分析如下:
if(customer != null){
UserDetails userDetails = new User(customer.getUsername(),customer.getPassword(),authorityList);
return userDetails;
}else{
throw new UsernameNotFoundException("此用户不存在");
}
}
}
接下来一步就是将权限信息存到SecurityContextHolder中,所以接下来补充JwtAuthenticationTokenFilter代码
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
//放行
filterChain.doFilter(request, response);
return;
}
//解析token
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey = "login:" + userid;
LoginUser loginUser = redisCache.getCacheObject(redisKey);
if(Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//存入SecurityContextHolder
//TODO 获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
具体的认证和授权流程我们就到此结束,接下来就是数据库的设计以及我们controller层中的代码编写。数据库中涉及用户、角色、菜单。菜单中有访问权限信息,将用户和角色绑定、将菜单和角色绑定就可以实现权限系统啦!
总结:
1、用户详情服务认证:
第一步:自己定义一个类去继承WebSecurityConfigurerAdapter。
第二步:去重写protected void configure(AuthenticationManagerBuilder auth) throws Exception方法
第三步:定义一个实现类去实现UserDetailsService
注意:查询到的权限信息不能直接使用,需封装成SimpleGrantedAuthority对象;最后返回的类型为UserDetails类,所以需要将最后的实体类封装成User()对象,因为User()实现了UserDetails,即返回的就是UserDetails对象。
最后一步:回到配置类中,使用UserDetailsService.
2、用户授权管理
en =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
具体的认证和授权流程我们就到此结束,接下来就是数据库的设计以及我们controller层中的代码编写。数据库中涉及用户、角色、菜单。菜单中有访问权限信息,将用户和角色绑定、将菜单和角色绑定就可以实现权限系统啦!
## 总结:
### 1、用户详情服务认证:
第一步:自己定义一个类去继承WebSecurityConfigurerAdapter。
第二步:去重写protected void configure(AuthenticationManagerBuilder auth) throws Exception方法
第三步:定义一个实现类去实现UserDetailsService
注意:查询到的权限信息不能直接使用,需封装成SimpleGrantedAuthority对象;最后返回的类型为UserDetails类,所以需要将最后的实体类封装成User()对象,因为User()实现了UserDetails,即返回的就是UserDetails对象。
最后一步:回到配置类中,使用UserDetailsService.
### 2、用户授权管理
直接重写protected void configure(HttpSecurity http) throws Exception,详细代码见上。