使用的环境:SpringBoot:2.6.13,JDK11,SpringSecurity:5.6.8(跟随springboot版本)
目录
入门案例
1、maven导坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、启动springboot项目后访问http://127.0.0.1:8080
所有的请求都要通过SpringSecurity的验证,这个登录验证界面是SpringSecurity自带的,账号是user,密码打印在控制台
自定义登录逻辑
1、编写BeanConfig配置类,添加Bean如下
@Configuration
public class BeanConfig {
// 用于自定义登录逻辑中的密码加密和验证密码
@Bean
public PasswordEncoder passwordEncoder (){
return new BCryptPasswordEncoder();
}
}
2、编写UserDetailsServerImpl实现类
package com.zwf.springsecurity.server.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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.stereotype.Service;
@Service
public class UserDetailsServerImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("执行了UserDetailsServerImpl中的loadUserByUsername方法");
// 1.查询数据库判断用户是否存在,如果不存在,抛出异常
if (!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在");
}
// 2.把查询出来的密码(注册时已经加密过)进行解析,或者直接把密码放到构造方法中
String password = passwordEncoder.encode("123");
// admin表示拥有管理员权限,normal表示拥有普通权限
return new User(username, password,
AuthorityUtils.commaSeparatedStringToAuthorityList("" + "admin,normal"));
}
}
自定义登录页面
1、编写login.html和main.html
login.html
<body>
<form action="/login" method="post">
用户名 <input type="text" name="username"> <br/>
密码 <input type="password" name="password"> <br/>
<input type="submit" value="登录">
</form>
</body>
如果没有配置,那么表单中的 input标签中name属性必须是username和password ,如果想要换成username123个password 123,那么需要在SpringSecurity配置类中配置:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表单提交
http.formLogin()
.usernameParameter("username")
.passwordParameter("password");
}
}
main.html
<body>
<h1>登陆成功</h1>
</body>
2、编写 LoginController 代码
@Controller
public class LoginController {
// 重定向到登录页面
@RequestMapping ("/login")
public String index() {
return "redirect:main.html";
}
// 重定向到登录成功页面
@RequestMapping ("/toMain")
public String toMain() {
return "redirect:main.html";
}
}
3、编写BeanConfig类
@Configuration
public class BeanConfig {
// 用于自定义登录逻辑中的密码加密和验证密码
@Bean
public PasswordEncoder passwordEncoder (){
return new BCryptPasswordEncoder();
}
}
4、编写SpringSecurity配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 自定义登录页面
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表单提交
http.formLogin()
// 自定义登录页面路径
.loginPage("/login.html")
// 当发现是login请求时,去执行UserDetailsServerImpl,必须和html表单的请求路径一样
.loginProcessingUrl("/login")
// 登录成功后跳转到指定controller路径,必须是post请求
.successForwardUrl("/toMain");
// 授权认证
http.authorizeRequests()
// 放开登录页面(这个一定要写在前面,不然报错)
.antMatchers("/login.html").permitAll()
// 所有请求都需要认证
.anyRequest().authenticated();
// 关闭csrf保护,类似防火墙
http.csrf().disable();
}
}
失败跳转
1、编写error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body>
<h1>登录失败,请重新登录</h1>
<a href="login.html">点击跳转登录</a>
</body>
</html>
2、编写SpringSecurity配置类,添加如下代码
// 表单提交
http.formLogin()
// 自定义登录页面路径
.loginPage("/login.html")
// 当发现是login请求时,去执行UserDetailsServerImpl,必须和html表单的请求路径一样
.loginProcessingUrl("/login")
// 登录成功后跳转到指定controller路径,必须是post请求
.successForwardUrl("/toMain")
// 登录失败后跳转到指定controller路径,必须是post请求
.failureForwardUrl("/toError");
// 授权认证
http.authorizeRequests()
// 放开login.html和error.html页面(这个一定要写在前面,不然报错)
.antMatchers("/error.html").permitAll()
.antMatchers("/login.html").permitAll()
// 所有请求都需要认证
.anyRequest().authenticated();
自定义处理器(适合前后端分离,或者跳转其他页面)
登录成功的:
1、编写新的handler代码
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(url);
}
}
2、编写配置类,比如跳转到百度
http.formLogin()
.successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"));
登录失败的:
1、编写新的handler代码
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
public MyAuthenticationFailureHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendRedirect(url);
}
}
配置类中的常用方法详解
http.authorizeRequests()
// 放开不拦截 login.html和error.html页面和静态资源(这个一定要写在前面,不然报错)
.antMatchers("/login.html").permitAll()
// permitAll()表示全部人都可以访问
.antMatchers("/img/**", "/css/**").permitAll()
// 按照正则表达式放开不拦截
.requestMatchers(".+[.]png")
// 所有请求都需要认证
.anyRequest().authenticated() ;
如果在yml中配置:(在所有controller都加前置路径xxx)
spring:
mvc:
servlet:
path: xxx
http.authorizeRequests()
// 放开controller中login路径,前缀路径是xxx
.mvcMatchers("/login").servletPath("xxx").permitAll();
- 访问控制权限:
- permitAll() 全部人都可以访问
- denyAll() 禁止所有用户访问受保护的资源
- anonymous 可以匿名访问
- authenticated() 允许已经通过身份验证的用户访问受保护的资源
- fullyAuthenticated() 需要完全认证(多出认证)才能访问
- rememberMe() 记住我的功能(之后免密登录)
权限、角色、IP判断
权限访问:
1、在配置类中:
http.authorizeRequests()
// 拥有admin权限才能访问admin.html
.antMatchers("/admin.html").hasAnyAuthority("admin");
2、在UserDetailsServerImpl为用户设置拥有admin权限
@Service
public class UserDetailsServerImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("执行了UserDetailsServerImpl中的loadUserByUsername方法");
// 1.查询数据库判断用户是否存在,如果不存在,抛出异常
if (!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在");
}
// 2.把查询出来的密码(注册时已经加密过)进行解析,或者直接把密码放到构造方法中
String password = passwordEncoder.encode("123");
// admin表示拥有管理员权限,normal表示拥有普通权限
return new User(username, password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
}
}
角色判断:
1、在配置类中:
http.authorizeRequests()
// 拥有admin权限才能访问admin.html
.antMatchers("/admin.html").hasAnyAuthority("admin",);
2、在在UserDetailsServerImpl为用户设置角色
// admin和normal表示拥有管理员权限和普通权限,ROLE_a是设置角色a,必须以ROLE_开头
return new User(username, password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_a"));
IP判断:
http.authorizeRequests()
// 只有127.0.0.1才能访问admin.html
.antMatchers("/main.html").hasIpAddress("127.0.0.1")
自定义403处理
1、编写新的handle代码:
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler
{
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 设置响应码403
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
// 设置响应头
response.setHeader("Content-Type", "application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("{\"code\":\"403\", \"status\":\"error\", \"msg\":\"权限不足,请联系管理员\"}");
writer.flush();
writer.close();
}
}
2、在配置类中引用:
@Autowired
MyAccessDeniedHandler myAccessDeniedHandler;
// 异常处理
http.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler);
自定义实现权限控制(结合access)
之前是使用http.authorizeRequests()..anyRequest().authenticated()来配置所有请求都认证,现在我们自定义实现权限的控制
1、编写server类:
@Service
public class MyServerImpl implements MyServer {
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object obj = authentication.getPrincipal();
if (obj instanceof UserDetails){
UserDetails userDetails = (UserDetails) obj;
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
}
return false;
}
}
2、编写配置类:
http.authorizeRequests()
.anyRequest().access("@myServerImpl.hasPermission(request,authentication)");
3、在UserDetailsServerImpl类中给用户设置权限:
return new User(username, password,
AuthorityUtils.commaSeparatedStringToAuthorityList(
"admin,normal,ROLE_a,/admin.html"));
使用注解配置
使用注解配置需要先在启动类或者配置类中添加@EnableGlobalMethodSecurity(securedEnabled = true) 的注解来开启注解配置
- @Secured("ROLE_a") :在类或者方法上使用,多在Controller中的方法上对接口使用,表示 只有a 角色才能访问,否则报500
-
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)注解中的prePostEnabled = true表示开启方法执行前验证,比如在controller中:
@PreAuthorize("hasRole('a')") // 只有a角色才能访问
@RequestMapping ("/toMain")
public String toMain() {
return "redirect:main.html";
}
RememberMe - 记住我功能
1、该功能需要JDBC和mysql驱动的依赖,可以导入mybatis和mysql驱动的依赖来间接导入
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- mysql数据库依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2、编写BeanConfig
@Configuration
public class BeanConfig {
@Autowired
private DataSource dataSource;
// 用于自定义登录逻辑中的密码加密和验证密码
@Bean
public PasswordEncoder passwordEncoder (){
return new BCryptPasswordEncoder();
}
// 自定义记住我的功能持久化
@Bean
public PersistentTokenRepository getPersistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
// 设置数据源
jdbcTokenRepository.setDataSource(dataSource);
// 自动创建表,第一次启动时候需要,第二次启动需要注释掉,否则报错
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
3、在SecurityConfig类中配置该功能
@Autowired
private UserDetailsServerImpl userDetailsServer;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
// 记住我功能
http.rememberMe()
// 设置过期时间,单位是秒
.tokenValiditySeconds(60)
// 自定义登录逻辑
.userDetailsService(userDetailsServer)
// 持久层对象
.tokenRepository(persistentTokenRepository);
4、在login.html页面中添加记住我的单选框,
<form action="/login" method="post">
用户名 <input type="text" name="username"> <br/>
密码 <input type="password" name="password"> <br/>
记住我 <input type="checkbox" name="remember-me" value="true" /> <br/>
<input type="submit" value="登录"> <br/>
</form>
表单中name值只能是remember-me,除非去SecurityConfig中修改配置,当勾选记住我并登陆成功时,数据库会新增数据
在Thymeleaf中获取security属性值和判断权限
1、导入thymeleaf 相关依赖
<!-- thymeleaf SpringSecurity5 -->
<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>
2、在templates目录下创建demo.html
<!DOCTYPE html>
<!--添加HTML、Thymeleaf的Spring Security扩展模块的命名空间-->
<html lang="en"
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>
<!--获取security中的属性值-->
登录账号:<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/>
<br>
<!--根据security中用户权限来显示按钮-->
通过权限判断:
<button sec:authorize="hasAuthority('insert')">新增</button>
<button sec:authorize="hasAuthority('delete')">删除</button>
<button sec:authorize="hasAuthority('update')">修改</button>
<button sec:authorize="hasAuthority('select')">查看</button><br/>
通过角色判断:
<button sec:authorize="hasRole('a')">新增</button>
<button sec:authorize="hasRole('a')">删除</button>
<button sec:authorize="hasRole('a')">修改</button>
<button sec:authorize="hasRole('a')">查看</button>
</body>
</html>
3、在UserDetailsServerImpl的loadUserByUsername方法中释放权限
return new User(username, password,
AuthorityUtils.commaSeparatedStringToAuthorityList(
"admin,normal,ROLE_a,/admin.html,insert,delete"));
4、编写controller代码
@RequestMapping("/demo")
public String thymeleafDemo() {
return "demo";
}
登录访问后
退出登录
1、在main.html中添加security的登录退出超链接
<a href="/logout">退出登录</a>
2、在SecurityConfig类中配置该功能
//退出登录
http.logout()
// 退出成功后跳转到login.html
.logoutSuccessUrl("/login.html");
CSRF跨站保护
CSRF 跨站请求伪造,也被称为“OneClick Attack"或者Session Riding通过伪
造用户请求访问受信任站点的非法请求访问。
跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。
客户端与服务进行交互时,由于 http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。
1、在templates目录下创建login.html,并加入隐藏_csrf.token隐藏输入框
<!DOCTYPE html>
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
templates中的login.html
<form action="/login" method="post">
<!-- 隐藏输入框,value为security生成的_csrf.token,如果_csrf为空就不填入这个值 -->
<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="checkbox" name="remember-me" value="true" /> <br/>
<input type="submit" value="登录"> <br/>
</form>
</body>
</html>
2、编写controller
@RequestMapping("/showLogin")
public String showLogin() {
return "login";
}
3、编写SecurityConfig中的configure方法
// 关闭csrf保护,类似防火墙
// http.csrf().disable();
// 表单提交
http.formLogin()
// 自定义登录页面路径
.loginPage("/showLogin")
// 当发现是login请求时,去执行UserDetailsServerImpl,必须和html表单的请求路径一样
.loginProcessingUrl("/login")
// 授权认证
http.authorizeRequests()
// 放开不拦截 login.html和error.html页面和静态资源(这个一定要写在前面,不然报错)
.antMatchers("/showLogin").permitAll()
//退出登录
http.logout()
// 退出成功后跳转到login.html
.logoutSuccessUrl("/showLogin.html");
登录时会自动获取_csrf字段的值填入,且_csrf的值每次登陆都会变化
Oauth2协议
简介
第三方认证技术方案最主要是解决认证协议的通用标准问题,因为要实现跨系统认证,各系统之间要遵循一定的接口协议。
OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP、JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间,因而OAUTH是简易的。互联网很多服务如Open API,很多大公司Google,Yahoo,Microsoft等都提供了OAUTH认证服务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。
优点:
- 更安全,客户端不接触用户密码,服务器端更易集中保护广泛传播并被持续采用
- 短寿命和封装的token
- 资源服务器和授权服务器解耦集中式授权,简化客户端
- HTTP/IJSON友好,易于请求和传递token考虑多种客户端架构场景
- 客户可以具有不同的信任级别
缺点:
- 协议框架太宽泛,造成各种实现的兼容性和互操作性差
- 不是一个认证协议,本身并不能告诉你任何用户信息
Oauth2四种授权模式
- 授权码模式(该模式用得最多,也是最难最复杂的)
- 密码模式
- 客户端模式
- 隐式授权模式 / 简化授权模式
具体可以看详解,点击跳转:授权模式详解
授权码模式演示
1、编写授权服务器配置AuthorizationServerConfig
@Configuration
@EnableAuthorizationServer // 开启oauth2客户端模式(授权服务器模式)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 客户端id (一般由服务器生成),这里演示,使用固定值
.withClient("admin")
// 客户端密码,这里演示,使用固定值
.secret(passwordEncoder.encode("112233"))
// 访问token有效时间,单位秒
.accessTokenValiditySeconds(3600)
// 配置uri,用于授权成功后跳转
.redirectUris("http://www.baidu.com")
// 配置申请的授权范围
.scopes("all")
// 授权模式为授权码模式
.authorizedGrantTypes("authorization_code");
}
}
2、编写资源服务器配置ResourcesServerConfig
@Configuration
@EnableResourceServer
public class ResourcesServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestMatchers()
.antMatchers("/user/**");
}
}
3、编写配置BeanConfig
@Configuration
public class BeanConfig {
// 用于自定义登录逻辑中的密码加密和验证密码
@Bean
public PasswordEncoder passwordEncoder (){
return new BCryptPasswordEncoder();
}
}
4、编写SecurityConfig配置类
@Configuration
// 开启使用注解security的方法级别的权限控制
//@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
// 开启Web安全性支持,Spring Security的默认配置将会被应用
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭csrf保护,类似防火墙
http.csrf().disable()
// 以下是授权认证
.authorizeRequests()
.antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
// 连接一起写
.and()
// 运行所有表单请求
.formLogin().permitAll();
}
}
5、自定义实现User类
public class User implements UserDetails {
private String username;
private String password;
private List<GrantedAuthority> authorities;
public User(String username, String password, List<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.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;
}
}
6、自定义登录逻辑
import com.securityoauth2demo.pojo.User;
@Service
public class UserServer implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password = passwordEncoder.encode("123456");
return new User("admin", password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
7、编写controller测试携token令牌访问
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication) {
return authentication.getPrincipal();
}
}
8、访问http://127.0.0.1:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all,验证成功后在跳转url中code后的值来获取授权码
9、用授权码区获取token令牌
在Auth选项中选择Basic Auth
10、拿着token令牌就可以访问controller资源了
密码模式演示
1、在SecurityConfig配置类中添加密码模式要使用的Bean
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
2、编写授权服务器配置AuthorizationServerConfig
@Configuration
@EnableAuthorizationServer // 开启oauth2客户端模式(授权服务器模式)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserServer userServer;
/**
* 使用密码模式所需配置
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userServer);
}
// 配置客户端信息,根据配置信息访问url来获取授权码,这里的url是:
// http://127.0.0.1:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 客户端id (一般由服务器生成),这里演示,使用固定值
.withClient("admin")
// 客户端密码,这里演示,使用固定值
.secret(passwordEncoder.encode("112233"))
// 访问token有效时间,单位秒
.accessTokenValiditySeconds(3600)
// 配置uri,用于授权成功后跳转
.redirectUris("http://www.baidu.com")
// 配置申请的授权范围
.scopes("all")
// 授权模式为授权码模式
// .authorizedGrantTypes("authorization_code")
// 授权模式为密码模式
.authorizedGrantTypes("password");
}
}
3、密码模式访问获取token令牌
4、拿着token令牌就可以访问资源了
Redis存储Token
1、导入maven依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2对象池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、编写RedisConfig类
@Configuration
public class RedisConfig {
// redis连接工厂
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 自动将token存储到redis中
* @return
*/
@Bean
public TokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
}
3、在授权服务器配置AuthorizationServerConfig中新增代码
@Autowired //按类型注入
@Qualifier("redisTokenStore") //按名字注入
private TokenStore tokenStore;
/**
* 使用密码模式所需配置
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userServer)
.tokenStore(tokenStore);
}
4、获取token的时候自动存入redis
JWT - demo
1、导依赖
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2、编写Test
/**
* JWT生成token
*/
@Test
void testJwtBuilder() {
// 当前时间
long now = System.currentTimeMillis();
// 过期时间为10分钟
long exp = now + 60 * 1000 * 10;
// 构建JWT对象
JwtBuilder jwtBuilder = Jwts.builder();
jwtBuilder
// 声明的标识,相当于{"jwi":"8888"}
.setId("8888")
// 主体,用户 {"sub:":"Rose"}
.setSubject("Rose")
// 创建时间
.setIssuedAt(new Date())
// 算法选用HS256,盐是xxxx
.signWith(SignatureAlgorithm.HS256, "xxxx")
// 设置过期时间
.setExpiration(new Date(exp))
// 自定义声明
.claim("roles", "admin")
.claim("logo", "xxx.jpg");
// 获取JWT的token
String token = jwtBuilder.compact();
System.out.println(token);
String[] split = token.split("\\.");
System.out.println("=====================================================");
// 头
System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
// 主体
System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
// 签名部分无法解密
System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
}
/**
* 解析token
*/
@Test
void testParseToken() {
String token = "";
Claims claims = Jwts.parser()
// 签名秘钥(盐)
.setSigningKey("xxxx")
.parseClaimsJws(token)
.getBody();
System.out.println( "id : "+claims.getId());
System.out.println("subject : "+claims.getSubject());
System.out.println("issuedAt : "+claims.getIssuedAt());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("签发时间:"+simpleDateFormat.format(claims.getIssuedAt()));
System.out.println("过期时间:"+simpleDateFormat. format(claims .getExpiration()));
System.out.println("当前时间: "+simpleDateFormat.format(new Date()));
System.out.println("roles:"+claims.get("roles"));
System.out.println("logo:"+claims.get("logo"));
}
SecurityOauth2整合Jwt
1、注释掉redis的依赖和配置代码
2、创建JwtTokenStoreConfig配置类
@Configuration
public class JwtTokenStoreConfig {
@Bean
public TokenStore jwtTokenstore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
*
* jwt与oauth2的token转换器
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
//配置JwT使用的秘钥
accessTokenConverter.setSigningKey("test_key");
return accessTokenConverter;
}
}
3、修改AuthorizationServerConfig配置
@Autowired
@Qualifier("jwtTokenstore")
private TokenStore tokenStore;
@Autowired
JwtAccessTokenConverter jwtAccessTokenConverter;
/**
* 使用密码模式所需配置
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userServer)
// 配置存储令牌策略
.tokenStore(tokenStore)
// 配置令牌转换器
.accessTokenConverter(jwtAccessTokenConverter);
}
5、获取token来访问资源
扩展jwt中的存储内容
1、创建JwtTokenEnhancer配置类
@Configuration
public class JwtTokenEnhancer implements TokenEnhancer {
/**
* Jwt token内容增强
* @param oAuth2AccessToken
* @param oAuth2Authentication
* @return
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String, Object> info = new HashMap<>();
info.put("enhance","enhance info");
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(info);
return oAuth2AccessToken;
}
}
2、在JwtTokenStoreConfig配置类中添加Bean
/**
* 自定义jwt的token增强器
* @return
*/
@Bean(name = "JwtTokenStoreConfig-JwtTokenEnhancer") //依赖包中已经由同名的Bean,所有重命名
public JwtTokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}
3、修改AuthorizationServerConfig授权服务器配置
@Autowired
JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 配置JWT内容增强器
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(delegates);
// token配置
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userServer)
// 配置存储令牌策略
.tokenStore(tokenStore)
// 配置jwt与security的令牌转换器
.accessTokenConverter(jwtAccessTokenConverter)
// 配置JWT内容增强器
.tokenEnhancer(tokenEnhancerChain);
}