SpringSecurity快速入门
SpringSecurity快速入门
一、简介
利用springIOC(控制反转)、AOP、DI(依赖注入),为项目设置安全的认证、授权、会话管理、密码加密等技术,减少权限代码量,增加可移植性。
二、快速入门
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
- 前端登录页面(放在/resources/static/中)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页</title>
</head>
<body>
<!--name值必须为username和password-->
<form action="login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="checkbox" value="true" name="remember"> 记住我
<button type="submit">登录</button>
</form>
</body>
</html>
- 配置文件
server:
port: 9090
servlet:
context-path: /security
#前缀使用默认: /static/ /public/ /resource/
登录用户名:user 密码在控制台随机生成,每次不一样,登录成功后跳转到自定义登录页面。
三、配置业务逻辑层
- 配置处理登录用户名和密码的类
package com.zwf.spring_security.service;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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 org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @author Mr Zeng
* @version 1.0
* @date 2023-09-29 10:40
*/
@Service
public class UserDetailService implements UserDetailsService {
@Resource
private PasswordEncoder getPwd;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//获取前台的username值
if(!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在!");
}
//配置角色名的时候要加上ROLE_前缀 角色名和资源名严格区分大小写
//可配置多个用逗号分割。
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,normal,ROLE_root,ROLE_sys,/security/successLogin,/security/main.html");
//构造参数有用户名 密码 权限名(不为空)
User user = new User(username,getPwd.encode("123456"),authorities);
return user;
}
}
配置核心配置类(里面配置授权和认证)
@EnableWebSecurity
public class PermissionConfig {
@Resource
private AccessDeniedHandler denyAccessExceptionDemo;
@Resource
private PersistentTokenRepository getPersistent;
@Resource
private UserDetailService userDetailService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests((auth)->{
auth.antMatchers("/login.html").permitAll()
.antMatchers("/error.html").permitAll()
//静态资源被放行 ant表达式:?表示一个字符 * 表示0个或者多个字符
// **表示多级目录 HttpMethod.GET表示不认证get请求的页面
.antMatchers(HttpMethod.GET,"/js/**","/img/**","/css/**").permitAll()
//不是简单的多层路径 而是在servlet中配置的<url-patten>/authencation</url-patten>
//需要在yml文件中配置 spring.mvc.servlet.path=/authencation 表示/authencation/login放行
//只有mvcMatchers()才有servletPath()这个方法
// .mvcMatchers("/login").servletPath("/authencation").permitAll()
//自定义访问类
//用正则表达式定义文件路径 去匹配
// .regexMatchers(".+[.]js").permitAll()
//配置只有admin角色才能访问 这里的角色名不能加前缀 ROLE_ 严格区分大小写
// .antMatchers("/make.html").hasRole("admin")
//配置多个角色 只要有一个角色满足就可以访问
// .antMatchers("/make.html").hasAnyRole("admin","root","sys")
//只有拥有normal资源的才能访问 hasAnyAuthority()配置多个资源名
// .antMatchers("/make.html").hasAuthority("normal")
//access使用
// .antMatchers("/make.html").access("hasAnyRole('admin')")
//定义特定IP访问 在authorizeRequests中
.antMatchers().hasIpAddress("127.0.0.1")
//所有请求都要被认证授权就是登录后才能访问
.anyRequest().authenticated();
//所有请求都要被认证授权就是登录后才能访问 传入登录请求URI
// .anyRequest()
// .access("@myAuthentiCationImpl.isAuthenti(request,authentication)");
})//放行页面的
.formLogin()
.usernameParameter("username") //可以通过这个方法对表单name值对应
.passwordParameter("password")
.loginPage("/login.html")
.loginProcessingUrl("/login")
//successForwardUrl只能实现请求转发链接,不能实现重定向!
.successForwardUrl("/demo1")
//自定义成功跳转方法实现重定向 一般前后端分离的项目就是使用重定向访问数据
//自定义类与successForwardUrl不能共存
// .successHandler(new SuccessRedirectHandle("/security/successLogin"))
//只能实现请求转发
// .failureForwardUrl("/errordemo")
//登录失败页面也是一样 可以自定义实现重定向 与failureForwardUrl不同存在
.failureHandler(new FailRedirectHandle("error.html"))
.and()
.exceptionHandling().accessDeniedHandler(denyAccessExceptionDemo)
.and()
//默认保存2周 记住我
.rememberMe()
.rememberMeParameter("remember")
.tokenValiditySeconds(60) //设置失效时间 60s
.tokenRepository(getPersistent)
.userDetailsService(userDetailService)
.and()
.logout().logoutUrl("/logout")
.logoutSuccessUrl("/goLogin")
.and()
.csrf().disable(); //关闭CSRF
return http.build();
}
}
UserDetails是一个接口,处理用户名和密码的内置接口,要想返回 UserDetails 的实例就只能返回接口的实现类。SpringSecurity 中提供了如下的
实例。对于我们只需要使用里面的 User 类即可。注意 User 的全限定路径是:org.springframework.security.core.userdetails.User 此处经常和系统中自己开发的User 类弄混。
密码处理类
Spring Security 要求容器中必须有 PasswordEncoder 实例。所以当自定义登录逻辑时要
求必须给容器注入 PaswordEncoder 的bean对象。
方法:
encode() :把参数按照特定的解析规则进行解析。
matches() :验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。
如果密码匹配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
upgradeEncoding() :如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false。
官方推荐BCryptPasswordEncoder实现类,BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认 10.
配置类
@Configuration
public class PasswordEncodeUtilsConf{
@Bean
public PasswordEncoder getPwd(){
return new BCryptPasswordEncoder();
}
}
测试使用
@Service
public class UserDetailService implements UserDetailsService {
@Resource
private PasswordEncoder getPwd;
......
}
四、配置自定义登录成功页
- 常用授权配置类中的方法
登录页 (必须为post请求,否则无法跳转,视图解析器不支持post请求,需要引入模板引擎或者使用重定向跳转页面!)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页</title>
</head>
<body>
<!--name值必须为username和password-->
<form action="login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="checkbox" value="true" name="remember"> 记住我
<button type="submit">登录</button>
</form>
</body>
</html>
登录逻辑层
@Service
public class UserDetailService implements UserDetailsService {
@Resource
private PasswordEncoder getPwd;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//获取前台的username值
if(!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在!");
}
//配置角色名的时候要加上ROLE_前缀 角色名和资源名严格区分大小写
//可配置多个用逗号分割。
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,normal,ROLE_root,ROLE_sys,/security/successLogin,/security/main.html");
//构造参数有用户名 密码 权限名(不为空)
User user = new User(username,getPwd.encode("123456"),authorities);
return user;
}
}
controller层
package com.zwf.spring_security.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author Mr Zeng
* @version 1.0
* @date 2023-09-29 10:38
*/
@Controller
public class LoginController {
//视图解析器不支持post请求
//登录成后跳转的路径
@RequestMapping("/successLogin")
public String goLoginDo(){
return "redirect:main.html"; //重定向访问
}
//拥有ROLE_admin或者admin角色的才能访问
// @Secured("admin")
@PreAuthorize("hasRole('ROLE_admin')")
@RequestMapping("/goMake")
public String goMake(){
return "redirect:make.html"; //重定向访问
}
@RequestMapping("/errordemo")
public String goErrorPage(){
return "redirect:error.html"; //重定向访问
}
@RequestMapping("/exception")
public void exception(HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
writer.print("{'code':'403','msg':'权限不足!','data',''}");
writer.flush();
writer.close();
}
@RequestMapping("/demo1")
public String goDemo(){
return "demo"; //使用thymeleaf模板引擎后的访问方法
}
@RequestMapping("/goLogin")
public String goLogin(){
return "redirect:login.html";
}
}
自定义登录成功跳转处理配置类
public class SuccessRedirectHandle implements AuthenticationSuccessHandler {
private String pageUrl;
public SuccessRedirectHandle() {
}
public SuccessRedirectHandle(String pageUrl) {
this.pageUrl = pageUrl;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//可以在页面重定向跳转时 进行一些授权操作
//获取登录对象的用户详情对象
User user = (User)authentication.getPrincipal();
System.out.println("登录用户名:"+user.getUsername());
String remoteHost = request.getRemoteHost();
System.out.println(remoteHost);
//由于考虑到密码安全 所以密码获取不到
System.out.println("登录密码:"+user.getPassword());
response.sendRedirect(pageUrl);
}
}
登录配置类(在核心配置类中,前提要放行登录页、错误页)
http.formLogin()
.usernameParameter("username") //可以通过这个方法对表单name值对应
.passwordParameter("password")
.loginPage("/login.html")
.loginProcessingUrl("/login")
//successForwardUrl只能实现请求转发链接,不能实现重定向!
.successForwardUrl("/demo1")
//自定义成功跳转方法实现重定向 一般前后端分离的项目就是使用重定向访问数据
//自定义类与successForwardUrl不能共存
// .successHandler(new SuccessRedirectHandle("/security/successLogin"))
//只能实现请求转发
// .failureForwardUrl("/errordemo")
//登录失败页面也是一样 可以自定义实现重定向 与failureForwardUrl不同存在
.failureHandler(new FailRedirectHandle("error.html"))
访问登录页 不会使用默认登录页 会直接跳转到自定义登录页!
五、自定义成功处理类
由于默认的successForwardUrl只能实现请求转发链接,不能实现重定向!需要自定义实现重定向,实现前后端分离接口跨域访问!
自定义成功处理类
package com.zwf.spring_security.pagehandler;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Mr Zeng
* @version 1.0
* @date 2023-09-29 16:27
*/
public class SuccessRedirectHandle implements AuthenticationSuccessHandler {
private String pageUrl;
public SuccessRedirectHandle() {
}
public SuccessRedirectHandle(String pageUrl) {
this.pageUrl = pageUrl;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//获取登录对象的用户详情对象
User user = (User)authentication.getPrincipal();
System.out.println("登录用户名:"+user.getUsername());
String remoteHost = request.getRemoteHost();
System.out.println(remoteHost);
//由于考虑到密码安全 所以密码获取不到
System.out.println("登录密码:"+user.getPassword());
response.sendRedirect(pageUrl);
}
}
在核心配置类中使用(springboot2.7及以上版本使用以下方法)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http..formLogin().successHandler(new SuccessRedirectHandle("/security/successLogin"))
...
}
六、自定义登录失败页
定义原因跟成功跳转页的局限性一样!
自定义登录失败跳转页
package com.zwf.spring_security.pagehandler;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Mr Zeng
* @version 1.0
* @date 2023-09-29 16:47
*/
//重写失败页面跳转重定向方法
public class FailRedirectHandle implements AuthenticationFailureHandler {
private String pageUrl;
public FailRedirectHandle(String pageUrl) {
this.pageUrl = pageUrl;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendRedirect(pageUrl);
}
}
在核心配置类文件中使用(2.7及以上版本的springboot用以下方法授权)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin().failureHandler(new FailRedirectHandle("error.html"))
...
}
七、访问控制URL方法(在springsecurity核心配置类中)
http.formLogin():可以通过里面的内置方法自定义登录页面
http.authorizeRequests():主要对URL进行控制,也就是我们说的授权,支持链式编程,可以通过内置方法对一些请求进行放行(比如静态文件等)。
http.authorizeRequests().anyRequest().authenticated():表示所有请求都要认证,必须放在最后面。
常用的方法
- antMatches(String pattern,...):可以匹配多个路径,支持ant表达式,也可以匹配具体路径。
- regexMatchers(String pattern,....):可以匹配多个正则表达式。
内置对象可用作方法参数,对具体请求方法的请求路径进行认证。
- HttpMethod枚举类
- MvcMatches()
使用与配置sevelet.contextPath路径情况。
spring.mvc.servlet.path=/xxxx
方法基本使用sevletPath()方法只有mvcMatchers()方法拥有,如果使用其他方法只需使用多层路径/XX/..
.mvcMatchers("/demo").servletPath("/xxxx").permitAll()
内置访问控制方法
-
permitAll()表示所匹配的URL任何人都允许访问
-
authenticated()表示所匹配的URL都需要被认证才能访问。
-
anonymous()表示可以匿名访问匹配的URL,和permitAll()效果类似,只是设置为anonymous()的url会执行filter链中。
-
denyAll()表示匹配的URL都不允许被访问
-
rememberMe():表示勾选记住我才能被访问。
-
fullyAuthenticated()表示没有使用记住我功能才能被访问。
角色权限判断方法,参数值严格区分大小写。
- hasAuthority(String):表示拥有这个权限的用户才能访问该请求。
.antMatchers("/main.html").hasAuthority("admin");//拥有admin权限的用户可以访问main.html页面
- hasAnyAuthority(String ...):可以写多个权限名,进行认证
- hasRole(String):表示拥有此角色的用户才能访问,需要在service中授权角色名,注意需要加
ROLE_
前缀。
.antMatchers("/main.html").hashasRole("root");
-
hasAnyRole(String...):可以配置多个角色名进行认证!
-
hasIpAddress(String ip):可以配置主机Ip认证,严格区分Ip,localhost和127.0.0.1是不一样的,通过request.getRemoteAddr()获取Ip地址。
八、自定义403异常页面
出现403响应码一般就是权限不足!
//自定义类通过IOC容器实例化然后依赖注入
http.exceptionHandling().accessDeniedHandler(denyAccessExceptionDemo);
自定义异常页面(要实现 AccessDeniedHandler接口)
@Component
public class DenyAccessExceptionDemo implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType("application/json;charset=utf8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
PrintWriter writer = response.getWriter();
writer.write("{'msg':'权限不足!','code':'403'}");
writer.flush();
writer.close();
}
}
九、Access()方法使用
Access()方法中参数可以直接写内置授权认证方法,比如hasRole()、hasAuthentication()方法。
常用的内置方法
基本使用方式
自定义方法使用
- 自定义方法类()
@Service
public class MyAuthentiCationImpl implements AuthentiCationDemo{
@Override
public Boolean isAuthenti(HttpServletRequest request, Authentication authentication) {
Object obj = authentication.getPrincipal();
if(obj instanceof UserDetails){
UserDetails user=(UserDetails) obj;
//获取登录成功后用户的所有权限
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI())); //获取请求资源名
}
return false;
}
}
- 使用自定义方法(通过@Bean id名再获取相应方法实例进行使用)
// /security/successLogin对访问请求路径进行授权通过!
.anyRequest() .access("@myAuthentiCationImpl.isAuthenti(request,authentication)");
十、基于注解的访问控制
前提要在springboot启动类上开启注解配置
@SpringBootApplication
//开启注解支持 securedEnabled表示开启@Secure()注解 prePostEnabled表示开启@preMethod和@postMethod @PreAuthorize()表示方法执行前操作 @PostAuthorize()表示方法执行后操作
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true)
public class SpringSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
一般在Controller层使用
//@Secured参数前缀ROLE_可写可不写,表示拥有ROLE_admin或者admin角色的才能访问此方法
@Secured("admin")
@PreAuthorize("hasRole('ROLE_admin')") //@PreAuthorize()和@PostAuthorize()参数是方法名
@RequestMapping("/goMake")
public String goMake(){
return "redirect:make.html";
}
十一、RememberMe功能实现
一般使用此功能,表示可以免登录,保存用户的登录状态!
- 编写RememberMe配置类
@Configuration
public class RememberMeDemo {
@Autowired
private DataSource dataSource; //使用数据库连接 所以注入datasource数据源对象
@Bean
public PasswordEncoder getPwd(){
return new BCryptPasswordEncoder();
}
@Bean
public PersistentTokenRepository getPersistent(){
JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
repository.setDataSource(dataSource);
//自动建表 执行完一次要注释 项目已启动就会在数据库中建表保存登录成功后的用户登录状态信息
//重复使用程序会报错
repository.setCreateTableOnStartup(true);
return repository;
}
}
核心配置类中配置
http.rememberMe()
.rememberMeParameter("remember") //设置特定的表单name值
.tokenValiditySeconds(60) //设置失效时间 60s 默认是2周
.tokenRepository(getPersistent) //DI依赖注入持久层类,也就是以上自定义类
.userDetailsService(userDetailService) //自定义登录用户逻辑处理类
前端代码 (采用复选框设置记住我页面)
<!--name值必须为username和password 必须是post请求方式-->
<form action="login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<!--默认name值是remember-me-->
<input type="checkbox" value="true" name="remember"> 记住我
<button type="submit">登录</button>
</form>
十二、thymeleaf整合security使用
- 先导入依赖
<!--thymeleaf模板引擎-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
在html中编写声明
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
xmlns:th="http://www.thymeleaf.org">
在html页面中通过sec:authentication=" "获取UsernamePasswordAuthenticationToken中所有getXXX内容,包含父类的getXXX的内容
- name:登录用户名
- principal:登录主体,在自定义登录逻辑中是UserDetails
- credentials:凭证
- authorities:权限和角色
- details:实际上就是webAuthenticationDetails实例,可以获取remoteAddress和sessionId。
快速使用
!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>thymeleaf页面</title>
</head>
<body>
登录账号:<span sec:authentication="name"></span><br/>
登录账号:<span sec:authentication="principal.username"></span>
<br/>
凭证:<span sec:authentication="credentials"></span><br/>
权限和角色:<span sec:authentication="authorities"></span><br/>
客户端地址:<span sec:authentication="details.remoteAddress">
</span><br/>
sessionId:<span sec:authentication="details.sessionId"></span>
<br/>
<a href="logout">注销</a>
</body>
thymeleaf控制转发必须使用模板引擎视图解析器,跳转到相应页面
用户自定义设置请求路径权限
前端使用效果
十三、登录注销
实际上就是执行/logout请求,然后底层删除cookie和销毁session。
在核心配置类中使用
http.logout().logoutUrl("/logout") //处理注销请求的路径
.logoutSuccessUrl("/goLogin");//注销成功跳转到的页面
前端模拟页面
<a href="logout">注销</a>
十四、Csrf跨域请求伪造
CSRF跨站请求伪造,也称为“OneClick Attack”或者session Riding。通过伪造用户请求访问受信任站点的非法请求访问。
跨域:只要网络协议、IP地址,端口号中任何一个不相同就是跨域请求。
由于客服端与服务进行交互时,http本身是无状态协议,所以通过cookie记录客户端身份。在cookie中会存放session Id用来识别客户端身份的,在跨域的情况下,sessionId可能被第三方恶意劫持,通过sessionId向服务器发送请求,服务以为请求是合法的,可能会发生数据泄露等现象。
默认是开启crsf防护,如果我们不关闭就无法请求登录,或者通过模板引擎视图解析器跳转到登录页面的时候获取Crsf令牌。
<form action="/login" method="post">
<!--使用Csrf安全防护,获取服务器生成的csrf码 然后请求到服务器 登录成功后就可以访问权限
注意必须通过请求转发跳转到登录页 才能获取_csrf.token值
-->
<input type="hidden" name="_csrf" th:value="${_csrf.token}" th:if="${_csrf}">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
如果登录页没有设置Csrf 秘钥 就必须在核心配置类中关闭Csrf防护
http.csrf().disable(); //关闭csrf防护
十五、Oauth2认证方式
简介
Oauth2是一种通用安全接口协议,为用户提供了一个简易开放的标准。同时oauth2兼容多个语言,比如java、php等,使用Oauth2协议,用户不用接触用户名和密码进行授权,比较安全!比如微信在第三方应用认证授权的行为就是属于Oauth2认证授权,用户通过扫码授权,然后到授权服务器获取授权码,然后再用授权码获取令牌,然后使用令牌对资源服务器进行访问,资源服务器对令牌进行校验,校验通过,就响应资源给用户,具体流程如下图:
常用术语
- 客户凭证:客户端clientId和密码用于认证客户
- tokens:授权服务器接收客户请求后,颁发的访问令牌。
- 作用域(scopes):客户请求访问令牌时,由资源拥有者额外的细分权限。
- 授权码:仅用于授权码授权类型,用于交换获取访问令牌和刷新令牌。
- 访问令牌:用于代表一个用户或者服务直接去访问受保护的资源。
- 刷新令牌:用于去授权服务器获取一个刷新访问令牌。
- bearerToken:不管谁拿到Token都可以访问资源,类似现金。
- proof of posssession token:校验client是否对Token有明确的拥有权。
授权模式
- 授权码授权模式 authorization_code(常用)
- 简化授权模式 implicit
- 密码授权模式 password
客户端授权模式
刷新令牌授权模式(令牌过期后,通过刷新令牌直接获取新的令牌,简化了操作)
spring security oauth2架构图
客户通过用户代理访问授权服务器,在授权点进行授权,在授权服务器令牌业务逻辑类进行获取授权码,然后通过授权码再访问授权码获取令牌,然后携带令牌访问资源服务器,资源服务器对令牌进行校验,校验成功后获取资源。
十六、springBoot security oauth2 授权码模式
导入依赖(服务端)
<!--导入解析jwt秘钥的依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--spring-security-oauth2自动装配依赖-->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!--spring-security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
编写用户自定义详情类
public class User implements UserDetails {
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities ;
}
@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;
}
}
用户权限业务处理类
@Service
public class LoginService implements UserDetailsService {
@Resource
private PasswordEncoder getBcryptPwd;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(username==null||"".equals(username)){
throw new UsernameNotFoundException("用户名不存在!");
}
//这里对登录后进行授权 授权角色要加ROLE_前缀
return new User("root",getBcryptPwd.encode("root"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,ROLE_root,normal"));
}
}
Controller类资源请求路径
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication){
return authentication;
}
}
编写权限配置类
@Configuration
@EnableWebSecurity //开启授权配置
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/oauth/**","/login/**","logout/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
//密码加密类
@Bean
public PasswordEncoder getBcryptPwd(){
return new BCryptPasswordEncoder();
}
//用于密码授权获取令牌
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
配置授权服务器配置类(服务端)
@Configuration
@EnableAuthorizationServer//开启认证服务器
public class AuthentiServerConfig extends AuthorizationServerConfigurerAdapter {
@Resource
private PasswordEncoder getBcryptPwd;
//注入认证管理
@Autowired
private AuthenticationManager authenticationManager;
//注入用户自定义业务处理类
@Autowired
private LoginService userDetailsService;
//注入令牌保存器
// @Resource
// private TokenStore getRedisStoreDemo;
//jwt秘钥
@Autowired
private TokenStore jwtTokenStore;
//jwt转换器依赖注入
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
//TokenHancer 依赖注入
@Autowired
private TokenHancerConfig tokenHancerConfig;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置客户端在内存中运行
clients.inMemory()
//配置clientId
.withClient("admin")
//配置密码
.secret(getBcryptPwd.encode("admin"))
//配置token有效期 3600s
.accessTokenValiditySeconds(3600)
//设置属性刷新token有效期 3600s
//下次直接通过 grant_type=refresh refresh_token="" 请求获得有效token
.refreshTokenValiditySeconds(3600)
.autoApprove(true)
//配置授权成功后跳转到客户端登录页面进行授权
//.redirectUris("http://localhost:9998/login")
//模拟测试跳转到百度页面获取授权码 授权会在跳转链接后展示在浏览器路径框
.redirectUris("http://www.baidu.com")
//配置配置申请的范围
.scopes("all")
//配置授权类型 (授权码授权)
// .authorizedGrantTypes("authorization_code");
//授权方式 可以配置多个 "refresh_token","authorization_code"
.authorizedGrantTypes("authorization_code","refresh_token");
}
//配置密码访问授权所需要的配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//创建TokenEnhancerChain
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
//把 自定义enhancer 和 转换器放入
List<TokenEnhancer> list=new ArrayList<>();
list.add(tokenHancerConfig);
list.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(list);
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
// .tokenStore(getRedisStoreDemo);//把oauth2 token存入redis中
.tokenStore(jwtTokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(tokenEnhancerChain);
}
//使用单点登录必须配置
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//获取秘钥需要身份认证 使用单点登录必须配置!
security.tokenKeyAccess("isAuthenticated()");
}
}
配置资源服务器
//开启资源服务配置
@Configuration
@EnableResourceServer //开启资源服务配置
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//配置需要保护的资源
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and() //注意and()隔开
.requestMatchers()
.antMatchers("/user/**"); //配置需要保护的访问资源请求路径
}
}
用链接进行测试
http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_u ri=http://www.baidu.com&scope=all
跳转到登录先登录
登录成功后进行授权操作
跳转到百度页面 获取授权码
根据授权码在postMan进行测试 (获取令牌必须用post请求方式)
请求参数如下
携带令牌参数访问资源路径获取资源
令牌失效
- 密码授权模式
在以上架构基础上 授权服务配置类中配置加上以下
//注入用户自定义业务处理类
@Autowired
private LoginService userDetailsService;
//注入认证管理
@Autowired
private AuthenticationManager authenticationManager;
//在授权服务器配置方法中配置以下
//授权方式 可以配置多个 "refresh_token","authorization_code" 配置密码授权模式
.authorizedGrantTypes("password","refresh_token");
//配置密码访问授权所需要的配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
PostMan测试通过密码模式获取令牌 必须用post请求
通过令牌获取资源
十七、令牌保存在redis中
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>-->
<artifactId>spring-boot-starter-data-redis</artifactId>-->
</dependency>
- 配置redis连接工厂类
@Configuration
public class RedisStoreConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
//TokenStore 把oauth2 token存入redis中
@Bean
public TokenStore getRedisStoreDemo(){
return new RedisTokenStore(redisConnectionFactory);
}
}
- 授权服务器配置类
//注入用户自定义业务处理类
@Autowired
private LoginService userDetailsService;
//注入令牌保存器 redis配置类中的Bean
@Resource
private TokenStore getRedisStoreDemo;
//注入认证管理
@Autowired
private AuthenticationManager authenticationManager;
//配置密码访问授权所需要的配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenStore(getRedisStoreDemo); //把oauth2 token存入redis中
}
- 在yml配置redis连接信息
server:
port: 9999
spring:
redis:
host: 192.168.147.110
password: root@123456
postMan测试密码模式获取令牌
令牌保存在redis中
十八、常见的认证模式
http basic Auth:传统的用户名、密码登录
cookie Auth:把用户信息放进cookie中,cookie中的信息与服务器中session比对
Oauth:访问第三方应用时,无需用户名密码,通过签发的令牌验证登录。
Token Auth:用户通过用户名和密码登录成功后,会签发一个token令牌,token令牌保存在cookie中,每次访问资源路径时就携带令牌数据,资源服务器对令牌进行校验,校验成功后获取资源。
Token可以跨域访问、Cookie不能跨域访问,Token因为不用依靠Cookie,因此不需要Csrf。
十九、JWT简介
简介
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
JWT组成成分
请求头、载荷、签名。
三部分都是由Base64加密,签名是由请求+载荷+盐值经过Base64加密算法加密而来,因此盐值要保密,不要泄露,否则很容易被破解签名。
三部分通过.连接Base64加密后形成JWT
请求头
负载(载荷)
签证
二十、JWT快速入门
导入依赖
<!--用于解析jwt秘钥-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- 测试JWT常用方法
@SpringBootTest
class JwtDemoApplicationTests {
//测试创建Token
@Test
public void testCreateToken(){
JwtBuilder builder = Jwts.builder();
//声明的标识 => {jti:8888}
builder.setId("8888")
//用户主体 {sub:Rose} 公钥面向用户
.setSubject("Rose")
//创建时间 {ita:XXX}
.setIssuedAt(new Date())
//设置签名手段壹以及盐值 必须使用HS开头的加密方式
.signWith(SignatureAlgorithm.HS256,"ssssss");
//获取Token
String token = builder.compact();
System.out.println(token);
//使用BASE64解密
String[] split = token.split("\\.");
System.out.println("加密方式:"+Base64Codec.BASE64.decodeToString(split[0]));
System.out.println("[标识,用户主体,时间]:"+Base64Codec.BASE64.decodeToString(split[1]));
System.out.println("签名内容:(无法显示)"+Base64Codec.BASE64.decodeToString(split[2]));
}
//jwt Token解密解析声明的信息
@Test
void decodeJwtToken(){
String taken="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY5NjA4MTUxNX0.uWcsx0HJSpc87usI2AiVfulUvPw4AwEiSbUqQxuMt5A";
Claims cls = Jwts.parser()
//设置盐值
.setSigningKey("ssssss")
//设置token
.parseClaimsJws(taken)
//获取声明内容
.getBody();
//打印声明的属性
System.out.println("jwt标识:"+cls.getId());
System.out.println("jwt主体:"+cls.getSubject());
System.out.println("jwt时间:"+cls.getIssuedAt());
}
//设置过期时间 增加声明
@Test
public void expireJwt(){
Long currentTime=System.currentTimeMillis();
Long expireDate=currentTime+60*1000; //当前时间后的一分钟就是过期时间 单位毫秒
String token = Jwts.builder().setId("8888")
.setSubject("Rose")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "ABCD")
.setExpiration(new Date(expireDate))//设置过期时间
//增加声明
.claim("user","admin")
.claim("pwd","yyyyyy")
.compact();
System.out.println(token);
}
//获取token声明的信息
@Test
void parseJwt(){
String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY5NjA4MjgzNCwiZXhwIjoxNjk2MDgyODk0LCJ1c2VyIjoiYWRtaW4iLCJwd2QiOiJ5eXl5eXkifQ.WbxVv32zbBMcRQSALqoIQX5dSUEDUSjXJBxYz6f5j1U";
Claims body = Jwts.parser()
.setSigningKey("ABCD")
.parseClaimsJws(token)
.getBody();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("[声明的标识]"+body.getId());
System.out.println("[声明的主体]"+body.getSubject());
System.out.println("[声明的时间]"+sdf.format(body.getIssuedAt()));
System.out.println("[当前时间]"+sdf.format(new Date()));
System.out.println("[设置的过期时间]"+sdf.format(body.getExpiration()));
System.out.println("[获取的增加的声明]"+body.get("user"));
System.out.println("[获取的增加的声明]"+body.get("pwd"));
}
}
二十一、springsecurity Oauth2整合Jwt
导入依赖
<!--使用oauth2-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!--spring-oauth2自动装配依赖-->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!--springSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--用于解析jwt声明信息-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
配置JWT核心配置类
//配置JWt配置类
@Configuration
public class JwtConfig {
//token转换器 用于jwt令牌转换为Oauth2令牌
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jatc=new JwtAccessTokenConverter();
//配置jwt使用的秘钥key值
jatc.setSigningKey("zwf");
return jatc; //
}
//设置TokenStore保存器
@Bean
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
//把自定义的token增强类 注入Bean中
@Bean
public TokenHancerConfig tokenHancerConfig(){
return new TokenHancerConfig();
}
}
在授权服务器配置类中配置
@Configuration
@EnableAuthorizationServer//开启认证服务器
public class AuthentiServerConfig extends AuthorizationServerConfigurerAdapter {
//配置密码加密方式
@Resource
private PasswordEncoder getBcryptPwd;
//注入认证管理
@Autowired
private AuthenticationManager authenticationManager;
//注入用户自定义业务处理类
@Autowired
private LoginService userDetailsService;
//jwt秘钥 自定义jwt配置类
@Autowired
private TokenStore jwtTokenStore;
//jwt转换器依赖注入
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置客户端在内存中运行
clients.inMemory()
//配置clientId
.withClient("admin")
//配置密码
.secret(getBcryptPwd.encode("admin"))
//配置token有效期 3600s
.accessTokenValiditySeconds(3600)
//设置属性刷新token有效期 3600s
//下次直接通过 grant_type=refresh refresh_token="" 请求获得有效token
.refreshTokenValiditySeconds(3600)
.autoApprove(true)
//配置授权成功后跳转到客户端页面
.redirectUris("http://localhost:9998/login")
//配置配置申请的范围
.scopes("all")
//配置授权类型 (授权码授权)
// .authorizedGrantTypes("authorization_code");
//密码授权 "refresh_token","authorization_code"
.authorizedGrantTypes("authorization_code");
}
//配置密码访问授权所需要的配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenStore(jwtTokenStore) //jwt令牌保存策略
.accessTokenConverter(jwtAccessTokenConverter); //配置jwt转换器转换器
}
}
PostMan使用密码授权模式获取Jwt
官网解析
二十二、Jwt内容增强器
自定义Token增强类
/秘钥增强配置
public class TokenHancerConfig implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
//Token增加的类容 用于加强Token秘钥复杂度
Map<String,Object> info = new HashMap<>();
info.put("enhance","enhance info");
//把参数强转
((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info);
return accessToken;
}
}
在jwt配置类中配置TokenEnhancer IOC实例对象
//把以上自定义的token增强类 注入Bean中 IOC容器托管
@Bean
public TokenHancerConfig tokenHancerConfig(){
return new TokenHancerConfig();
}
在授权服务器配置类中配置
//jwt转换器依赖注入
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
//TokenHancer 依赖注入
@Autowired
private TokenHancerConfig tokenHancerConfig;
//配置密码访问授权所需要的配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//创建TokenEnhancerChain
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
//把 自定义enhancer 和 jwtAccessTokenconverter转换器放入集合中
List<TokenEnhancer> list=new ArrayList<>();
list.add(tokenHancerConfig); //集合存放 自定义TokenHancer
list.add(jwtAccessTokenConverter); //集合保存 jwt转换器
//把集合内容放入 TokenEnhancerChain类中
tokenEnhancerChain.setTokenEnhancers(list);
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenStore(jwtTokenStore) //jwt token保存策略
.accessTokenConverter(jwtAccessTokenConverter)
//配置TokenEnhancerChain
.tokenEnhancer(tokenEnhancerChain);
}
二十三、解析Jwt声明信息
- 导入依赖
<!--用于解析jwt声明信息-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
- 自定义Controller类请求解析
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getCurrentUser")
public Object getPrincipal(Authentication authentication, HttpServletRequest request){
//解析token中的声明信息
String header = request.getHeader("Authorization");
//获取Token
String token = header.substring(header.indexOf("bearer") + 7);
System.out.println(token);
//解析jwt声明的信息 返回json字串
return Jwts.parser().setSigningKey("zwf".getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(token)
.getBody();
}
}
- 使用Postman模拟请求
二十四、Oauth2+Jwt模拟单点登录(SSO)
模拟服务端
- 导入依赖
<!--使用oauth2-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!--springSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>-->
<!--用于解析jwt声明信息-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
- 配置好相关配置类,具体见上文已经阐述
唯一注意的是,授权服务器配置类设置授权成功重定页面要设置为客户端请求路径
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置客户端在内存中运行
clients.inMemory()
//配置clientId
.withClient("admin")
//配置密码
.secret(getBcryptPwd.encode("admin"))
//配置token有效期 3600s
.accessTokenValiditySeconds(3600)
//设置属性刷新token有效期 3600s
//下次直接通过 grant_type=refresh refresh_token="" 请求获得有效token
.refreshTokenValiditySeconds(3600)
.autoApprove(true) //自动授权也可以不设置进行手动授权
//配置授权成功后跳转到客户端页面 (特别注意!!!!1)
.redirectUris("http://localhost:9998/login")
//配置配置申请的范围
.scopes("all")
//配置授权类型 (授权码授权)
// .authorizedGrantTypes("authorization_code");
//密码授权 "refresh_token","authorization_code"
.authorizedGrantTypes("authorization_code","password");
}
- 配置好自定义用户业务逻辑类,具体见上文!
模拟客户端
- 导入依赖
<!--使用oauth2-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!--springSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>-->
<!--用于解析jwt声明信息-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
- 配置访问资源Controller
/**
* @author Mr Zeng
* @version 1.0
* @date 2023-10-01 11:22
*/
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication){
return authentication;
}
}
- yml核心配置文件,一定要注意配置文件的key value不要写错了!
server:
port: 9998 #注意不要与服务端的端口产生冲突 服务端端口和客户端口不能一致
servlet:
session:
cookie:
name: oauth2-client #cookie冲突 导致登录验证不通过 多个客户端的必须配置
#全部都是从服务端获取的数据
security:
oauth2:
client:
client-id: admin #在授权服务器配置类中设置的ClientId
client-secret: admin #在授权服务器配置类中设置的密码
user-authorization-uri: http://localhost:9999/oauth/authorize #授权URI
access-token-uri: http://localhost:9999/oauth/token #获取令牌URI
resource:
jwt:
key-uri: http://localhost:9999/oauth/token_key #获取TOken_key
测试访问 请求客户端资源路径名 :http://localhost:9998/user/getCurrentUser
首先重定向跳转到服务端登录页面,使用用户自定义配置类中设置的用户名和密码进行登录,登录成功后,自动重定向跳转到http://localhost:9998/user/getCurrentUser,整个过程使用了授权码授权方式访问资源目录。
本文来自博客园,作者:戴莫先生Study平台,转载请注明原文链接:https://www.cnblogs.com/smallzengstudy/p/17739900.html