首页 > 其他分享 >SpringSecurity基本使用流程

SpringSecurity基本使用流程

时间:2023-08-24 21:34:29浏览次数:53  
标签:基本 用户 登录 流程 验证码 SpringSecurity 认证 security 权限

本文介绍SpringBoot项目如何整合SpringSecurity,记录使用SpringSecurity完成项目的登录、退出、以及权限管理的相关流程。

1、导包:导入Security,前后端交互用户凭证用的是JWT,需要导入jwt,另外登录需要用到验证码,验证码的存储需要用到redis;

<!-- springboot security -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- jwt -->
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.1</version>
</dependency>
<!-- 验证码 -->
<dependency>  
  <groupId>com.github.axet</groupId>
  <artifactId>kaptcha</artifactId> 
  <version>0.0.9</version>
</dependency>
<!-- redis -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、上一步安装好security后,启动项目会自动生成一个登录密码(可以在yml文件中配置账户/密码);客户端发起一个请求,会进入 Security 过滤器链,会跳到security默认的/login页面,输入用户名(user)/密码之后才能访问项目资源;

# 配置默认的账户/密码
spring:
  security:
    user:
      name: user
      password: 123

3、使用security实现用户认证:分为首次登录和访问资源认证

  • 首次登录认证:用户名、密码和验证码完成登录
  • 访问资源认证:请求头携带Jwt进行token认证
首次登录

security是通过UsernamePasswordAuthenticationFilter过滤器校验账户名/密码的,系统如果有验证码功能,则需要在UsernamePasswordAuthenticationFilter过滤器之前添加一个图片验证码过滤器CaptchaFilter

验证码相关代码
config/KaptchaConfig:定义验证码的样式
controller/AuthController:
// 获取验证码接口
@GetMapping("/captcha")
// 生成一个随机的key,将key 和验证码code存入redis,将验证码code转成Base64图片和key一起返回给前端;

使用postman访问/captcha会返回Unauthorized;security默认会阻止访问,需要在config/SecurityConfig中配置白名单;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static final String[] URL_WHITELIST = {
            "/login",
            "/logout",
            "/captcha",
            "/favicon.ico",
    };

    protected void configure(HttpSecurity http) throws Exception {
        http
            // 登录配置
            .formLogin()	// 使用security默认的表单提交方式
          
            // 配置拦截规则
            .and()
            .authorizeRequests()
            .antMatchers(URL_WHITELIST).permitAll()	// 白名单放行
            .anyRequest().authenticated() // 其他接口拦截
        ;
    }

}
CaptchaFilter:验证码认证过滤器

此时不会校验验证码是否正确,需要在SecurityConfig中添加校验验证码的过滤器CaptchaFilter

// 配置自定义的过滤器
.and()
.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class) // 配置captchaFilter过滤器在用户名密码过滤器之前

security/CaptchaFilter.java 做验证码校验的过滤器

common/exception/CaptchaException 验证码出错的时候返回异常信息

添加完过滤器后,发送/login 的post接口,此时需要添加 http.cors().and().csrf().disable(),否则只会接受到get请求,如果验证码错误抛出异常后,需要交给认证失败处理器LoginFailureHandler,否则还会走一个名为/login的get请求;无法将验证码错误异常返给前端,需要在认证失败处理器中将请求错误封装成Result返给前端;

LoginFailureHandler: 认证失败处理器

此时校验验证码错误时可以返回自定义Result错误结果给前端 {"msg":"验证码错误","code":400};同时也给用户名密码错误添加认证失败处理器,用户名密码错误也能处理返回 {"msg":"用户名或密码错误","code":400}

// config/SecurityConfig
// 登录配置
.formLogin()   // 使用security默认的表单提交方式
.failureHandler(loginFailureHandler)
LoginSuccessHandler:认证(登录)成功处理器

此时用户名密码为.yml中配置的user/123,假设验证码是正确的,提交表单登录,会提醒跨域,添加config/CorsConfig并配置SecurityConfig解决跨域

// SecurityConfig
http.cors().and().csrf().disable()

解决完跨域后再次登录,虽然用户名密码正确,但还是会返回security默认的登录页,原因是:登录成功,security默认跳转到/链接,但是又会因为没有权限访问/,所有又会跳到security默认的登录页,所以我们必须取消原先默认的登录成功之后的操作,根据spring security的流程,登录成功之后会走AuthenticationSuccessHandler,因此在登录之前,我们先去自定义这个登录成功操作类LoginSuccessHandler,并在SecurityConfig中添加认证成功处理器;在LoginSuccessHandler中生成jwt(编写JwtUtils类,并在.yml中添加jwt配置信息),返回给前端,用于二次认证;此时就可以返回自定义的Result给前端了。

{
    "msg":"操作成功",
    "code":200,
    "data":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNjg2NjQ1MTA3LCJleHAiOjE2ODcyNDk5MDd9.i4qrA6wtCM8XsS9axppTTTOoA2bmg9vGgmqDWddwzbewNV72aoAgJqOTk9K8BJHk8su-hJHgeOhtnnCIBeTt9A"}
}

此时访问其他接口还是会跳到默认的登录页

身份认证

登录成功之后,前端拿到jwt的信息,也就是token,前端封装axios,在请求header中添加token;后端进行用户身份识别的时候,通过请求头中获取jwt,然后解析出用户名,这样就可以知道是谁在访问接口了,然后判断用户是否有权限操作;

JWTAuthenticationFilter:自定义过滤器用来识别jwt

访问接口时会进入该过滤器校验token

在JWTAuthenticationFilter中,获取到用户名之后封装成UsernamePasswordAuthenticationToken,之后交给SecurityContextHolder参数传递authentication对象,这样后续security就能获取到当前登录的用户信息了,也就完成了用户认证

JwtAuthenticationEntryPoint:认证失败处理器

把认证过滤器和认证失败入口配置到SecurityConfig中

// 异常处理器
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint) // 认证失败入口配置
  
// 配置自定义的过滤器
.and()
.addFilter(jwtAuthenticationFilter())	// 添加认证过滤器

此时,登录成功后,访问其他的接口需要带上token,在JwtAuthenticationFilter中校验token,如果token为null,往过滤器下一步走,跳到jwtAuthenticationEntryPoint(jwt认证失败处理器),返回错误信息给前端;此外token异常或过期,抛出JwtException;token正确时,则认证成功,返回接口数据;如果有些接口加上了权限认证,则在登录封装token的时候查询用户的权限列表,将用户权限封装到token中,访问接口时会校验token是否有权限,有才能访问,后面会实现权限认证

实现用户名密码查库登录

此时用户名和密码是默认在配置在.yml文件中,需要实现查询数据库登录

将配置文件中默认用户密码删除;使用Security内置的BCryptPasswordEncoder生成一个加密的密码;

// config/SecurityConfig 配置
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder() {   
  return new BCryptPasswordEncoder();
}

在TestController中添加一个生成加密密码的接口,把/test/**添加白名单,访问接口生成加密的密码,将用户名和加密密码添加到数据库中;目前数据库中用户名密码为admin/123

要实现查询数据库登录,需要重新定义这个查用户数据的过程,需要重写UserDetailsService接口;因为security在认证用户身份的时候会调用UserDetailsService.loadUserByUsername()方法,因此我们重写了之后security就可以根据我们的流程去查库获取用户了。然后我们把UserDetailsServiceImpl配置到SecurityConfig中

@Autowired
UserDetailServiceImpl userDetailService;

/*
	* 配置userDetailService注入到security中;委托security实现查询数据库账号密码登录
	* */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(userDetailService);
}

security/UserDetailsServiceImpl

在UserDetailsServiceImpl类中,继承了UserDetailsService,复写了loadUserByUsername方法,首先根据用户名查询是否存在用户,没有则抛出异常;方法返回UserDetails,为了后面我们可能会调整用户的一些数据,需要自定义AccountUser去重写UserDetails,在AccountUser中可以自定义用户字段,AccountUser中封装了用户的权限信息,通过getUserAuthority(userId)方法传入用户id查询用户的权限信息,封装成List后封装到AccountUser中,该过程会自动校验密码(todo);正确则登录成功获取到token,否则返回报错信息。

return new AccountUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), getUserAuthority(sysUser.getId()));

public List<GrantedAuthority> getUserAuthority(Long userId){
    // 角色(ROLE_admin)、菜单操作权限 sys:user:list
    String authority = sysUserService.getUserAuthorityInfo(userId);  // ROLE_admin,ROLE_normal,sys:user:list,....
    return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
}

// getUserAuthorityInfo方法中获取用户的角色和菜单权限,返回一个以逗号拼接的字符串
接口权限控制

用户认证成功之后,我们就知道谁在访问系统接口,这时又有一个问题,就是这个用户有没有权限来访问我们这个接口,需要在两个地方赋予用户权限

1、用户登录,调用UserDetailsService.loadUserByUsername()方法时候可以返回用户的权限信息

2、接口调用进行身份认证过滤器时候JWTAuthenticationFilter,需要返回用户权限信息

使用注解实现接口的权限控制,Security内置的权限注解:

@PreAuthorize:方法执行前进行权限检查

@PostAuthorize:方法执行后进行权限检查

@Secured:类似于 @PreAuthorize

// 需要Admin角色权限
@PreAuthorize("hasRole('admin')")
// 添加用户的操作权限
@PreAuthorize("hasAuthority('sys:user:save')")

验证权限的流程:

1、用户登录或者调用接口时候识别到用户,并获取到用户的权限信息

2、注解标识Controller中的方法需要的权限或角色

3、Security通过FilterSecurityInterceptor匹配URI和权限是否匹配

4、有权限则可以访问接口,当无权限的时候返回异常交给AccessDeniedHandler操作类处理

此时访问没有权限的接口,发现还是可以访问,原因是SecurityConfig需要添加开启注解权限校验,否则无法识别@PreAuthorize注解

@EnableGlobalMethodSecurity(prePostEnabled = true)	// 开启注解权限校验

添加后重启,访问无权限的接口,返回{"code":400,"msg":"不允许访问","data":null},最后添加权限不足异常处理器AccessDeniedHandler

JwtAccessDeniedHandler:无权限处理器

config/SecurityConfig添加权限不足异常处理器

// 异常处理器
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) // 认证失败入口配置            
.accessDeniedHandler(jwtAccessDeniedHandler) // 权限不足

添加后访问无权限的接口,发现并没有走到jwtAccessDeniedHandler过滤器 --- todo

用户退出

添加logoutSuccessHandler

config/SecurityConfig

// 退出配置
.and()
.logout()
.logoutSuccessHandler(jwtLogoutSuccessHandler)

编写退出逻辑jwtLogoutSuccessHandler,返回退出成功信息

完结撒花!

最近看了一篇知乎上大佬讲解SpringSecurity的文章,讲解的比较清楚,贴上链接一起学习:
https://zhuanlan.zhihu.com/p/342755411?utm_medium=social&utm_oi=1343915562263547904

标签:基本,用户,登录,流程,验证码,SpringSecurity,认证,security,权限
From: https://www.cnblogs.com/meer/p/17652916.html

相关文章

  • 弘运通手机上期货开户的详细流程
    现在开户很方便,准备好身分证和银行卡,用手机下载期货开户云或者期货公司的交易软件,根据提示,填写个人信息、上传身分证照片和银行卡照片、视频验证等操作,十多分钟就能开好,无论在哪家期货公司开户流程都一样,都是通过期货开户云系统办理。手机上办理期货开户是很方便快捷的,手机上开户......
  • 原油期货开户条件和流程是什么?
    原油期货开户条件(适当性新规):原油期货的开户其实就是在商品期货账户的基础上开通原油期货的交易权限,开通权限后,投资者只需要登陆自己原来的商品期货账户即可交易原油期货了。原油期货在适当性新规下的开户条件分以下三种情况:情况一:客户账户近一年内累计交易过至少50个交易日满足......
  • 量子相关计算基本操作2
    几种电路恒等式举例:以下电路的作用是否相同? 由于所以第一个电路可以等价于(1)同时,由于  所以(1)电路可转化为 故二者等价。电路化简规则 举例:  ......
  • OKHttp3主流程再分析
    一、概述为什么要是用OKHttp3 总结下来就两个大的方面 一、成熟稳定 OkHttp距今已有10多年的历史,在Android中大量且广泛的应用,在大、中、小项目中无处不在。可以这样说,只要是一个Android项目,网络框架的底层必定是OKHttp 二、高效 1.OkHttp的底......
  • Android WiFi 扫描流程
    1.WiFiManagerpackages\modules\Wifi\framework\java\android\net\wifi\WifiManager.java startScan@DeprecatedpublicbooleanstartScan(){returnstartScan(null);}/**@hide*/@SystemApi@RequiresPermission(android.Ma......
  • Java流程控制
         ......
  • Linux 内核音频数据传递主要流程 (下)
    来而不往非礼也。前面看到了用户空间应用程序和DMAbuffer之间交换数据,并更新runtime->control->appl_ptr指针的过程,这里看一下硬件设备驱动程序在完成DMAbuffer和硬件设备的数据交换之后,更新runtime->status->hw_ptr的过程。用户空间应用程序,在内核的__snd_pcm_lib_xf......
  • Kafka快速实战以及基本原理详解
     这一部分主要是接触Kafka,并熟悉Kafka的使用方式。快速熟练的搭建kafka服务,对于快速验证一些基于Kafka的解决方案,也是非常有用的。一、Kafka介绍​ChatGPT对于ApacheKafka的介绍:ApacheKafka是一个分布式流处理平台,最初由LinkedIn开发并于2011年开源。它主要用于解决大规模......
  • 流程控制Scanner进阶和顺序结构
    Scanner进阶用简单地判断语句输入整数和小数,并打印出正确结果和错误结果packageScanner;importjava.util.Scanner;publicclassDemo03{publicstaticvoidmain(String[]args){//获取键盘数据Scannerscr=newScanner(System.in);i......
  • Citrix Virtual Apps and Desktops云桌面内网Storefront登录流程详解
    哈喽大家好,欢迎来到微信公众号虚拟化时代君(XNHCYL)。 大家好,我是虚拟化时代君,一位潜心于互联网的技术宅男。这里每天为你分享各种你感兴趣的技术、教程、软件、资源、福利……(每天更新不间断,福利不见不散)第1章前言本文主要介绍Citrix虚拟桌面通过CitrixStorefront(不过CitrixNe......