Spring Security
1. 安全框架概述2. Spring Security 简述3.Spring Security
3.1 创建项目3.2 项目依赖3.3 页面
3.3.1 login.html3.3.2 main.html 3.4 测试
3.4.1 启动项目3.4.2 打开浏览器 3.5 自定义登录逻辑
3.5.1 Security 的配置类3.5.2 UserDetailsService 的实现类3.5.3 重启测试 3.6 自定义登陆页面
3.6.1 login.html3.6.2 Security 的配置类3.6.3 Controller3.6.4 重启测试 3.7 自定义失败页面
3.7.1 error.html3.7.2 Security 的配置类3.7.3 Controller 3.8 设置请求账户和密码的参数名
3.8.1 login.html3.8.2 Security 的配置类 3.9 自定义登陆成功处理器
3.9.1 MyAuthenticationSuccessHandler3.9.2 Security 的配置类 3.10 自定义登陆失败处理器
3.10.1 MyAuthenticationFailureHandler3.10.2 Security 的配置类 3.11 权限判断
3.11.1 main1.html3.11.2 Security 的配置类 3.12 角色判断
3.12.1 Security 的配置类 3.13 IP地址判断
3.13.1 Security 的配置类 3.14 自定义403处理方案
3.14.1 MyAccessDeniedHandler3.14.2 Security 的配置类 3.15 access 自定义权限
3.15.1 MyService3.15.2 MyServiceImpl3.15.3 Security 的配置类 3.16基于注解的访问控制
3.16.1 @Serured 注解
3.16.1.1 Security 的配置类3.16.1.2 Controller3.16.1.3 启动类 3.16.2 @PreAuthorize & @PostAuthorize
3.16.2.1 启动类3.16.2.2 Controller 3.17 Remember Me 功能实现
3.17.1 添加依赖3.17.2 Security 的配置类3.17.3 application.yml 3.18 Thymeleaf 在 SpringSecurity 中的使用
3.18.1 添加依赖3.18.2 thymeleaf 命名空间和 security 命名空间3.18.3 demo.html3.18.4 Controller 3.19 在 thymeleaf 中进行权限判断
3.19.1 demo.html 3.20 logout 退出登录的操作
3.20.1 Security 的配置类3.20.2 main.html 3.21 Spring Security 中的 csrf
3.21.1 Controller3.21.2 Security 配置类 4. Oauth2 认证
4.1 常用术语4.2 令牌类型4.3 特点
4.3.1 优点4.3.2 缺点 4.4 授权模式
4.4.1 授权码模式4.4.2 简化模式4.4.3 密码模式4.4.4 客户端模式4.4.5 更新令牌 5. Spring Security Oauth2
5.1 添加依赖5.2 授权码模式
5.2.1 Security 配置类5.2.2 自定登录逻辑5.2.3 自定义 User5.2.4 授权服务器配置5.2.5 资源服务器配置5.2.6 启动测试
5.2.6.1 地址栏输入5.2.6.2 打开Postman 5.3 密码模式
5.3.1 Security 配置类5.3.2 授权服务器配置5.3.3 启动测试 5.4 Readis 存储 Token
5.4.1 Redis 配置类5.4.2 授权服务器配置5.4.3 启动测试 6. JWT
6.1 常见的认证机制
6.1.1 HTTP Basic Auth6.1.2 cookie Auth6.1.3 Token Auth
6.1.3.1 Token Auth 大概的流程6.1.3.2 Token Auth 的优点: 6.1.3 OAuth 6.2 JWT 简介
6.2.1 JWT令牌的优点6.2.2 JWT令牌的缺点 6.3 JWT的构成
6.3.1 头部(Header)6.3.2 载荷(Payload, 类似于飞机上承载的物品)6.3.2 签证(Signature)、签名
6.3.2.1 签名的目的 6.4 JJWT
6.4.1 添加依赖6.4.2 生成令牌6.4.3 解析令牌 7.Spring Security Oauth2 整合 JWT
7.1 JwtToken 配置类7.2 授权服务器配置7.3 扩展 JWT 里的内容存储
7.3.1 Jwt内容增强器7.3.2 JwtToken 配置类7.3.3 授权服务器配置 7.4 解析 Jwt 中的内容
7.4.1 添加依赖7.4.2 Controller 7.5 刷新令牌
7.5.1 授权服务器 8. Spring Security Oauth2 整合 SSO
8.1 添加依赖8.2 启动类8.3 Client01 的 Controller8.4 application.yml8.5 原来的 Spring Security Oauth2 的授权服务器配置8.5 操作演示
8.5.1 访问api
Spring SecuritySpring家族一员。是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Contro1,DI: Dependency Injection依赖注入)和AOP(面向切面编程〉功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
什么是安全框架?解决系统安全问题的框架。如果没有安全框架,我们需要手动处理每个资源的访问控制,非常麻烦。使用安全框架,我们可以通过配置的方式实现对资源的访问限制。
2. Spring Security 简述Spring Security是一个高度自定义的安全框架。利用Spring loC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。使用Spring Secruity的原因有很多,但大部分都是发现了javaEE的Servlet规范或EJB规范中的安全功能缺乏典型企业应用场景。同时认识到他们在WAR 或EAR 级别无法移植。因此如果你更换服务器环境,还有大量工作去重新配置你的应用程序。使用Spring Security解决了这些问题,也为你提供许多其他有用的、可定制的安全功能。正如你可能知道的两个应用程序的两个主要区域是“认证"和"授权”(或者访问控制)。这两点也是Spring Security重要核心功能。
认证是建立一个他声明的主体的过程(一个"主体"一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统),通俗点说就是系统认为用户是否能登录。
授权指确定一个主体是否允许在你的应用程序执行一个动作的过程。通俗点讲就是系统判断用户是否有权限去做某些事情。
3.Spring Security 3.1 创建项目 3.2 项目依赖3.3 页面 3.3.1 login.htmlorg.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.security spring-security-test test
登录
3.3.2 main.html
Home
登录成功
3.4 测试
3.4.1 启动项目
3.4.2 打开浏览器
http://localhost:8080/login.html
初始账号 user
初始密码每次启动都会改变
会跳转到Spring Security 提供的登陆页面
注:没有做任何配置
3.5 自定义登录逻辑 3.5.1 Security 的配置类package com.yuan.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder getPassWord() {
return new BCryptPasswordEncoder();
}
}
3.5.2 UserDetailsService 的实现类
package com.yuan.service;
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 UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("执行了loadUserByUsername方法");
//1.查询数据库,用户名称/账号是否存在,如果不存在就抛出UsernameNotFoundException异常
if (!"admin".equals(username)) throw new UsernameNotFoundException("用户名不存在");
//2.把查询的密码(注册时加密过)进行解析,或者直接把密码放入构造方法
String password = passwordEncoder.encode("123");
return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));
}
}
3.5.3 重启测试
http://localhost:8080/login.html
输入一个错的
输入正确的之后跳到了我们自己写的登陆页面
3.6 自定义登陆页面 3.6.1 login.html
Title
3.6.2 Security 的配置类
package com.yuan.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义登录页面
http.formLogin()
//当发现、login的时候认为是登录,去执行 UserDetailsServiceImpl->loadUserByUsername
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
//登陆成功要跳转的页面,必须是post请求
.successForwardUrl("/toMain")
;
//设置了自定义登陆页面之后,security提供的原始的认证将全部失效
//授权认证
http.authorizeRequests()
//登录页面,登陆失败页面都不需要认证
.antMatchers("/login.html").permitAll()
//所以要对所有的请求做拦截做认证【必须是登录之后才能被访问】
.anyRequest().authenticated()
;
//暂时理解为防火墙
//关闭csrf防护
http.csrf().disable();
}
@Bean
public PasswordEncoder getPassWord() {
return new BCryptPasswordEncoder();
}
}
3.6.3 Controller
@RequestMapping("/toMain")
public String toMain() {
return "redirect:main.html";
}
3.6.4 重启测试
http://localhost:8080/login.html
登陆后自动跳转
3.7 自定义失败页面 3.7.1 error.html
错误
登录失败
重新登录
3.7.2 Security 的配置类
package com.yuan.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义登录页面
http.formLogin()
//当发现、login的时候认为是登录,去执行 UserDetailsServiceImpl->loadUserByUsername
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
//登陆成功要跳转的页面,必须是post请求
.successForwardUrl("/toMain")
//登陆失败要跳转的页面,必须是post请求
.failureForwardUrl("/toError")
;
//设置了自定义登陆页面之后,security提供的原始的认证将全部失效
//授权认证
http.authorizeRequests()
//登录页面,登陆失败页面都不需要认证
.antMatchers("/login.html","/error.html").permitAll()
//所以要对所有的请求做拦截做认证【必须是登录之后才能被访问】
.anyRequest().authenticated()
;
//暂时理解为防火墙
//关闭csrf防护
http.csrf().disable();
}
@Bean
public PasswordEncoder getPassWord() {
return new BCryptPasswordEncoder();
}
}
3.7.3 Controller
@RequestMapping("/toError")
public String toError() {
return "redirect:error.html";
}
3.7.4 重启测试
http://localhost:8080/login.html
登陆失败后跳转
3.8 设置请求账户和密码的参数名 3.8.1 login.html
Title
3.8.2 Security 的配置类
package com.yuan.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义登录页面
http.formLogin()
.usernameParameter("usernamercy")
.passwordParameter("passwordrcy")
//当发现、login的时候认为是登录,去执行 UserDetailsServiceImpl->loadUserByUsername
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
//登陆成功要跳转的页面,必须是post请求
.successForwardUrl("/toMain")
//登陆失败要跳转的页面,必须是post请求
.failureForwardUrl("/toError")
;
//设置了自定义登陆页面之后,security提供的原始的认证将全部失效
//授权认证
http.authorizeRequests()
//登录页面,登陆失败页面都不需要认证
.antMatchers("/login.html", "/error.html").permitAll()
//所以要对所有的请求做拦截做认证【必须是登录之后才能被访问】
.anyRequest().authenticated()
;
//暂时理解为防火墙
//关闭csrf防护
http.csrf().disable();
}
@Bean
public PasswordEncoder getPassWord() {
return new BCryptPasswordEncoder();
}
}
3.9 自定义登陆成功处理器
3.9.1 MyAuthenticationSuccessHandler
package com.yuan.handle;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String success_url;
public MyAuthenticationSuccessHandler(String success_url) {
this.success_url = success_url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println(request.getRemoteAddr());
System.out.println(request.getRemoteAddr());
User principal = (User) authentication.getPrincipal();
System.out.println(principal.getUsername());
System.out.println(principal.getPassword());//因为安全的原因输出null
System.out.println(principal.getAuthorities());//权限
response.sendRedirect(success_url);
}
}
3.9.2 Security 的配置类
// .successForwardUrl("/toMain")
.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
3.10 自定义登陆失败处理器
3.10.1 MyAuthenticationFailureHandler
package com.yuan.handle;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
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;
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String failure_url;
public MyAuthenticationFailureHandler(String failure_url) {
this.failure_url = failure_url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendRedirect(failure_url);
}
}
3.10.2 Security 的配置类
// .failureForwardUrl("/toError")
.failureHandler(new MyAuthenticationFailureHandler("/error.html"))
3.11 权限判断
3.11.1 main1.html
main1
main1.html
3.11.2 Security 的配置类
.antMatchers("/main1.html").hasAuthority("admin")
3.12 角色判断
3.12.1 Security 的配置类
// .antMatchers("/main1.html").hasRole("abc")
.antMatchers("/main1.html").hasAnyRole("abc","123","chat")
3.13 IP地址判断
3.13.1 Security 的配置类
.antMatchers("/main1.html").hasIpAddress("127.0.0.1")
3.14 自定义403处理方案
3.14.1 MyAccessDeniedHandler
package com.yuan.handle;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
//设置相应的状态码
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setHeader("Content-Type", "application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("{"statue":"error","msg":"权限不足,请联系管理员"}");
writer.flush();
writer.close();
}
}
3.14.2 Security 的配置类
//403异常
http.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler);
3.15 access 自定义权限
3.15.1 MyService
package com.yuan.service;
import org.springframework.security.core.Authentication;
import javax.servlet.http.HttpServletRequest;
public interface MyService {
boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
3.15.2 MyServiceImpl
package com.yuan.service;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
@Service
public class MyServiceImpl implements MyService {
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
Collection extends GrantedAuthority> authorities = userDetails.getAuthorities();
return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
}
return false;
}
}
3.15.3 Security 的配置类
// .anyRequest().authenticated()
//httpServletRequest会报异常
.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)")
3.16基于注解的访问控制
在Spring Security中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过EnableGlobalMethodsecurity进行开启后使用。
如果设置的条件允许,程序正常执行。如果不允许会报500
org.springframework.security.access.AccessDeniedException:不允许访问
这些注解可以写到Service接口或方法上上也可以写到Controller或Controller的方法上。
通常情况下都是写在控制器方法上的,控制接口URL是否允许被访问。
@Secured是专门用于判断是否具有角色的。能写在方法或类上。参数要以ROLE_开头。
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@documented
public @interface Secured {
String[] value();
}
3.16.1.1 Security 的配置类
package com.yuan.config;
import com.yuan.handle.MyAccessDeniedHandler;
import com.yuan.handle.MyAuthenticationFailureHandler;
import com.yuan.handle.MyAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义登录页面
http.formLogin()
.usernameParameter("username")
.passwordParameter("password")
//当发现、login的时候认为是登录,去执行 UserDetailsServiceImpl->loadUserByUsername
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
//登陆成功要跳转的页面,必须是post请求
.successForwardUrl("/toMain")
// .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
//登陆失败要跳转的页面,必须是post请求
.failureForwardUrl("/toError")
// .failureHandler(new MyAuthenticationFailureHandler("/error.html"))
;
//设置了自定义登陆页面之后,security提供的原始的认证将全部失效
//授权认证
http.authorizeRequests()
//登录页面,登陆失败页面都不需要认证
.antMatchers("/login.html", "/error.html").permitAll()
.antMatchers("/js
String value();
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@documented
public @interface PostAuthorize {
String value();
}
3.16.2.1 启动类
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)3.16.2.2 Controller
// @Secured("ROLE_abc")
// @PreAuthorize支持以ROLE_开头,配置类不可以
@PreAuthorize("hasRole('abc')")
@RequestMapping("/toMain")
public String toMain() {
return "redirect:main.html";
}
3.17 Remember Me 功能实现
Spring Security中 Remember Me 为 记住我 功能,用户只需要在登录时添加remember-me复选框,取值为true。
Spring Security 会自动把用户信息存储到数据源中,以后就可以不登录讲行访问
3.17.2 Security 的配置类org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.0 mysql mysql-connector-java 8.0.26
package com.yuan.config;
import com.yuan.handle.MyAccessDeniedHandler;
import com.yuan.handle.MyAuthenticationFailureHandler;
import com.yuan.handle.MyAuthenticationSuccessHandler;
import com.yuan.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义登录页面
http.formLogin()
.usernameParameter("username")
.passwordParameter("password")
//当发现、login的时候认为是登录,去执行 UserDetailsServiceImpl->loadUserByUsername
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
//登陆成功要跳转的页面,必须是post请求
.successForwardUrl("/toMain")
// .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
//登陆失败要跳转的页面,必须是post请求
.failureForwardUrl("/toError")
// .failureHandler(new MyAuthenticationFailureHandler("/error.html"))
;
//设置了自定义登陆页面之后,security提供的原始的认证将全部失效
//授权认证
http.authorizeRequests()
//登录页面,登陆失败页面都不需要认证
.antMatchers("/login.html", "/error.html").permitAll()
.antMatchers("/js
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/oauth/authorize**", "/login
@Service
public class UserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password = passwordEncoder.encode("123");
return new User("admin", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
5.2.3 自定义 User
因为一些原因,is***()的方法改成了true,如果启动登录没有问题[锁定,失效 …],可以不改动【false】
package com.yuan.pojo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
public class User implements UserDetails {
private String username;
private String password;
private List authorities;
public User(String username, String password, List authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return 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;
}
}
5.2.4 授权服务器配置
package com.yuan.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client-id
.withClient("admin")
//配置client-secret
.secret(passwordEncoder.encode("112233"))
//配置访问token的有效期
// .accessTokenValiditySeconds(60 * 60)
//配置redirect-utl,用于授权成功后跳转
.redirectUris("http://www.baidu.com")
//配置申请的权限范围
.scopes("all")
//配置grant-type,授权类型,【授权码模式】
.authorizedGrantTypes("authorization_code")
;
}
}
5.2.5 资源服务器配置
package com.yuan.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestMatchers()
.antMatchers("/user
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client-id
.withClient("admin")
//配置client-secret
.secret(passwordEncoder.encode("112233"))
//配置访问token的有效期
// .accessTokenValiditySeconds(60 * 60)
//配置redirect-utl,用于授权成功后跳转
.redirectUris("http://www.baidu.com")
//配置申请的权限范围
.scopes("all")
//配置grant-type,授权类型,【授权码模式】
// .authorizedGrantTypes("authorization_code")
//配置grant-type,授权类型,【密码模式】
.authorizedGrantTypes("password")
;
}
}
5.3.3 启动测试
http://localhost:8080/oauth/token
访问我们写的Controller
http://localhost:8080/user/getCurrentUser5.4 Readis 存储 Token 5.4.1 Redis 配置类
package com.yuan.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
}
5.4.2 授权服务器配置
@Autowired
@Qualifier("redisTokenStore")
private TokenStore tokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
.tokenStore(tokenStore);
}
5.4.3 启动测试
http://localhost:8080/oauth/token
成功储存在redis里
6. JWT 6.1 常见的认证机制 6.1.1 HTTP Basic AuthHTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API使用的最简单的认证方式,只
需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避
免采用HTTP BasicAuth。
6.1.2 cookie Authcookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个cookie对象;通过客户端带上来cookie对象来与服务
器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie的expire time使cookie在一定时间
内有效。
6.1.3 Token Auth使用基于Token的身份验证方法,在服务端不需要存储用户的登录记录。
6.1.3.1 Token Auth 大概的流程- 客户端使用用户名跟密码请求登录服务端收到请求,去验证用户名与密码验证成功后,服务端会签发一个Token,再把这个Token发送给客户端4.客户端收到Token 以后可以把它存储起来,比如放在cookie里客户端每次向服务端请求资源的时候需要带着服务端签发的Token服务端收到请求,然后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据
比 HTTP Basic Auth 方式更安全,比 cookie Auth 方式更节约服务器资源,比 OAuth 方式更加轻量。
6.1.3.2 Token Auth 的优点:- 支持跨域访问
cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
- 无状态(服务端可扩展行)
Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
- 更适用CDN
可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.
- 去耦
不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
- 更适用于移动应用
当你的客户端是一个原生平台(iOS, Android,Windows 10等)时,cookie是不被支持的(你需要通过cookie容器进行处理),这时采用Token认证机制就会
简单得多。
- CSRF(跨站请求伪造Cross-site request forgery)
因为不再依赖于cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
- 性能
一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.
- 不需要为登录页面做特殊处理
如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.
- 基于标准化
你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:
Firebase,Google, Microsoft)
6.1.3 OAuthOAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用
户名和密码提供给第三方应用。
OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方
系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方
网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容
下面是OAuth2.0的流程:
这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用。
6.2 JWT 简介Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分
布式站点的单点登录(SSO)场景。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声
明信息,该token也可直接被用于认证,也可被加密。
6.2.1 JWT令牌的优点- jwt基于json,非常方便解析。可以在令牌中自定义丰富的内容,易扩展。通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。4.资源服务使用JWT可不依赖认证服务即可完成授权。
- JWT令牌较长,占存储空间比较大。
第一部分我们称它为头部(Header),第二部分我们称其为载荷(Payload, 类似于飞机上承载的物品),第三部分是签证(Signature).
6.3.1 头部(Header)jwt的头部承载两部分信息:
声明类型,这里是jwt声明加密的算法 通常直接使用 HMAC HS256
完整的头部就像下面这样的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ96.3.2 载荷(Payload, 类似于飞机上承载的物品)
- 标准中注册的声明(建议但不强制使用)
iss (issuer):签发人 exp (expiration time):过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号
- 公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
- 私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的claim。比如下面那个举例中的name都属于自定的claim。这些claim跟WT标准规定的claim区别在于:JWT规定的claim,JWT的接收方
在拿到WT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验
证以及规则才行。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
其中sub是标准的声明,name是自定义的声明(公共的或私有的)
然后将其进行base64加密,得到Jwt的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV96.3.2 签证(Signature)、签名
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)payload (base64后的)secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串(头部在前),然后通过header中声明的加密方式进行加盐secret组合
加密,然后就构成了jwt的第三部分:
UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q
注意: secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场
景都不应该流露出去。一旦客户端得知这个secret,那就意味着客户端是可以自我签发jwt了。
6.3.2.1 签名的目的最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。对于两个不同的输入,产生同样
的输出的概率极其地小(有可能比我成世界首富的概率还小)。所以,我们就把“不一样的输入产生不一样的输出”当做必然事件来看待吧。
所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服
务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。
服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器应用是怎么知道我们用的是哪一种算法呢?别忘了,我们在JWT的头部
中已经用alg字段指明了我们的加密算法了。
如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们
应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。
注意:在JWT中,不应该在载荷里面加入任何敏感的数据,比如用户的密码。
6.4 JJWT 6.4.1 添加依赖6.4.2 生成令牌4.0.0 org.springframework.boot spring-boot-starter-parent 2.6.2 com.yuan jjwtdemo 0.0.1-SNAPSHOT jjwtdemo jjwtdemo 1.8 io.jsonwebtoken jjwt 0.9.0 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-maven-plugin 2.6.2
@Test
public void testCreateToken() {
JwtBuilder jwtBuilder = Jwts.builder()
//声明标识("jti":”8888“)
.setId("8888")
//主题("sub":"xiaoyuan")
.setSubject("xiaoyuan")
//创建日期("ita":"*******")
.setIssuedAt(new Date())
//设置过期时间 10分钟
.setExpiration(new Date(System.currentTimeMillis() + 60 * 10000))
//设置签名
.signWith(SignatureAlgorithm.HS256, "yuan")
//自定义申明
.claim("roles","admin")
.claim("logo","***.jpg")
//可以直接传入map
// .addClaims(map)
;
String token = jwtBuilder.compact();
System.out.println(token);
System.out.println("===========================================");
String[] split = token.split("\.");
for (String s : split) System.out.println(base64Codec.base64.decodeToString(s));
}
6.4.3 解析令牌
@Test
public void testParseToken() {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoieGlhb3l1YW4iLCJpYXQiOjE2NDIzMDkzMTQsImV4cCI6MTY0MjMwOTkxNCwicm9sZXMiOiJhZG1pbiIsImxvZ28iOiIqKiouanBnIn0.vECoGRZ26pwmEeeKxPwfrTLzf7wQYOYSWqWv6oDohZE";
Claims claims = Jwts.parser().setSigningKey("yuan").parseClaimsJws(token).getBody();
System.out.println("id:" + claims.getId());
System.out.println("sub:" + claims.getSubject());
System.out.println("sub:" + claims.getSubject());
System.out.println("roles:" + claims.get("roles"));
System.out.println("logo:" + claims.get("logo"));
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()));
}
7.Spring Security Oauth2 整合 JWT
7.1 JwtToken 配置类
package com.yuan.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
public class JwtTokenStoreConfig {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey("TEST_KEY");
return jwtAccessTokenConverter;
}
}
7.2 授权服务器配置
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
//配置令牌存储策略
.tokenStore(tokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
;
}
7.3 扩展 JWT 里的内容存储
7.3.1 Jwt内容增强器
package com.yuan.config;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map map = new HashMap<>();
map.put("enhancer", "Enhancer Map");
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(map);
return oAuth2AccessToken;
}
}
7.3.2 JwtToken 配置类
@Bean
public JwtTokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}
7.3.3 授权服务器配置
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置Jwt内容增强器
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
//配置令牌存储策略
.tokenStore(tokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(tokenEnhancerChain)
;
}
7.4 解析 Jwt 中的内容
7.4.1 添加依赖
7.4.2 Controllerio.jsonwebtoken jjwt 0.9.0
@RequestMapping("/getJwtToken")
public Object getCurrentUser(Authentication authentication, HttpServletRequest httpServletRequest) {
String authorization = httpServletRequest.getHeader("Authorization");
System.out.println(authorization);
String token = authorization.substring(authorization.indexOf("bearer") + 7);
return Jwts.parser().setSigningKey("TEST_KEY".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token).getBody();
}
7.5 刷新令牌
7.5.1 授权服务器
.authorizedGrantTypes("password","authorization_code","refresh_token")
8. Spring Security Oauth2 整合 SSO
8.1 添加依赖
8.2 启动类4.0.0 org.springframework.boot spring-boot-starter-parent 2.6.2 com.yuan oauth2client01 0.0.1-SNAPSHOT oauth2client01 oauth2client01 1.8 org.springframework.security.oauth.boot spring-security-oauth2-autoconfigure org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test io.jsonwebtoken jjwt 0.9.0 org.springframework.cloud spring-cloud-dependencies Greenwich.SR6 pom import org.springframework.boot spring-boot-maven-plugin
@SpringBootApplication
//开启单点登录
@EnableOAuth2Sso
public class Oauth2client01Application {
public static void main(String[] args) {
SpringApplication.run(Oauth2client01Application.class, args);
}
}
8.3 Client01 的 Controller
package com.yuan.controller;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(("/user"))
public class UserController {
@RequestMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication) {
return authentication;
}
}
8.4 application.yml
server:
port: 8081
servlet:
session:
cookie:
# 防止cookie冲突,冲突导致验证不通过
name: OAUTH2-CLIENT-SESSIONID01
#授权服务器地址
oauth2-server-url: http://localhost:8080
#与授权服务器对应的地址
security:
oauth2:
client:
client-id: admin
client-secret: 112233
user-authorization-uri: ${oauth2-server-url}/oauth/authorize
access-token-uri: ${oauth2-server-url}/oauth/token
resource:
jwt:
key-uri: ${oauth2-server-url}/oauth/token_key
8.5 原来的 Spring Security Oauth2 的授权服务器配置
package com.yuan.config;
import com.yuan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.web.servlet.oauth2.resourceserver.OAuth2ResourceServerSecurityMarker;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置Jwt内容增强器
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
//配置令牌存储策略
.tokenStore(tokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(tokenEnhancerChain)
;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client-id
.withClient("admin")
//配置client-secret
.secret(passwordEncoder.encode("112233"))
//配置访问token的有效期
.accessTokenValiditySeconds(60 * 600)
//配置刷新令牌的有效期
.refreshTokenValiditySeconds(864000)
//配置redirect-utl,用于授权成功后跳转
.redirectUris("http://localhost:8081/login")
//设置自动授权
.autoApprove(true)
//配置申请的权限范围
.scopes("all")
//配置grant-type,授权类型,【授权码模式】
// .authorizedGrantTypes("authorization_code")
//配置grant-type,授权类型,【密码模式】
.authorizedGrantTypes("password", "authorization_code", "refresh_token")
;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//获取密钥需要身份认证,使用单点登录时候必须配置
security.tokenKeyAccess("isAuthenticated()")
;
}
}
8.5 操作演示
8.5.1 访问api
客户端client01的api
http://localhost:8081/user/getCurrentUser
跳转到了服务器上 Security 提供的登陆界面
http://localhost:8080/login
输入在服务器上配置的账号密码(注意:这里再授权服务器里配置了自动授权)
再次回到了client01的api访问接口上
http://localhost:8081/user/getCurrentUser
显示出了当前登录用户的信息



