SpringSecurity Oauth2Authentication对象使用
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity Oauth2Authentication对象使用相关的知识,希望对你有一定的参考价值。
参考技术A在调用资源服务器的过程中,我们会将申请的token 作为header值进行传递,携带调用者的身份信息。但是资源服务器是如何通过token对调用者的身份进行判断的呢?
Security中有一个Filter实现了对token信息的转换,将token值转换成了调用者的用户信息。该filter就是 Oauth2AuthenticationProcessingFilter
一、查看源码
查看Oauth2AuthenticationProcessingFilter的doFilter方法
通过查看Oauth2AuthenticationProcessingFilter的dofilter方法,重点有两点
(1)将request中的token提取出来封装成Authentication对象
(2)将Authentication交给authenticationManager进行鉴权处理
下面我们重点看下这两处的处理。
二、token到Authentication对象转换实现
Authentication authentication = tokenExtractor .extract(request);
tokenExtractor在Oauth2AuthencationProcessingFilter中的默认实现是BearerTokenExtractor,我们查看BearerTokenExtractor的extract()方法。
三、Authentication对象的鉴权
Authentication authResult = authenticationManager .authenticate(authentication);
此处的authenticationManager的实现类是Oauth2AuthenticationManager,而不是我们之前一直提到的ProvicerManager。我们看下Oauth2AuthenticationManager中的authenticate()方法。
RemoteTokenService 的 loadAuthentication() 方法
用户认证转换类
* 接口层注入的 OAuth2Authentication对象中的 principal属性即在该类的extractAuthentication() 方法中实现的。
* security默认使用的是 DefaultAccessTokenConverter类中的extractAuthentication()方法中使用。
* 通过继承UserAuthenticationConverter该类,实现其中的extractAuthentication()方法来满足我们自己构造 principal属性的需求。
* 在ResourceConfig类中,继续使用DefaultAccessTokenConverter,但是类中的UserAuthenticationConverter我们里换成我们自己的CustomUserAuthenticationConverter实现类。
*
* 我们构造的principal属性是map类,里面包含phone和userId两个字段。
轻松上手SpringSecurity,OAuth,JWT
目录
学习目标
一.SpringSecurity
1.SpringSecurity简介及快速入门
<1>.SpringSecurity简介
Spring Security是一个高度自定义的安全框架。利用 Spring IoC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。使用 Spring Secruity 的原因有很多,但大部分都是发现了javaEE的 Servlet 规范或 EJB 规范中的安全功能缺乏典型企业应用场景。同时认识到他们在 WAR 或 EAR 级别无法移植。因此如果你更换服务器环境,还有大量工作去重新配置你的应用程序。使用 Spring Security解决了这些问题,也为你提供许多其他有用的、可定制的安全功能。正如你可能知道的两个应用程序的两个主要区域是“认证”和“授 权”(或者访问控制)。这两点也是 Spring Security 重要核心功能。“认证”,是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统),通俗点说就是系统认为用户是否能登录。“授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。通俗点讲就是系统判断用户是否有权限去做某些事情
<2>.SpringSecurity快速入门
导入依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>springsecuritydemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecuritydemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--SpringSecurity组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--test组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- mysql 数据库依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!--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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
导入前端页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username123" /><br/>
密码:<input type="password" name="password123" /><br/>
记住我:<input type="checkbox" name="remember-me" value="true" /><br />
<input type="submit" value="登录" />
</form>
</body>
</html>
访问页面:
导入spring-boot-starter-security 启动器后,Spring Security 已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面。
默认的username为user,密码打印在控制台上,在浏览器中输入账号和密码后会显示 login.html 页面内容。
2.SpringSecurity基本原理
<1>.UserDetailsService详解
当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,只需要实现UserDetailsService 接口即可。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
返回值是一个UserDetails接口:
public interface UserDetails extends Serializable {
// ~ Methods
Collection<? extends GrantedAuthority> getAuthorities();//获取所有权限
String getPassword();//获取密码
String getUsername();//获取用户名
boolean isAccountNonExpired();//判断账号是否过期
boolean isAccountNonLocked();//判断账号是否被锁定
boolean isCredentialsNonExpired();//判断凭证(密码)是否过期
boolean isEnabled();//是否可用
}
这里我们一般返回UserDetails的实现类User
<2>.PasswordEncoder 密码解析器详解
Spring Security 要求容器中必须有 PasswordEncoder 实例。所以当自定义登录逻辑时要求必须给容器注入PaswordEncoder 的bean对象。
接口介绍:
public interface PasswordEncoder {
String encode(CharSequence rawPassword);//把参数按照特定的解析规则进行解析。
//:验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;
如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
boolean matches(CharSequence rawPassword, String encodedPassword);
//如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器
代码示例:
@Test
public void contextLoads() {
PasswordEncoder pe = new BCryptPasswordEncoder();
String encode = pe.encode("123");
System.out.println(encode);
boolean matches = pe.matches("1234", encode);
System.out.println("========================");
System.out.println(matches);
}
3. SpringSecurity自定义登录逻辑及权限控制
<1>.自定义逻辑登录
当 进 行 自 定 义 登 录 逻 辑 时 需 要 用 到 之 前 讲 解 的 UserDetailsService 和 PasswordEncoder 。但是Spring Security 要求:当进行自定义登录逻辑时容器内必须有 PasswordEncoder 实例。所以不能直接 new 对象。
代码示例:
第一步:编写配置类,注入PasswordEncoder Bean:
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
第二步:编写自定义登录逻辑(账号密码需要从数据库获取,这里使用模拟的方法)
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder pw;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("执行了loadUserByUsername方法");
//1.查询数据库判断用户名是否存在,如果不存在就会抛出UsernameNotFoundException异常
if (!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在!");
}
//2.把查询出来的密码(注册时已经加密过)进行解析,或者直接把密码放入构造方法
String password = pw.encode("123");
//加入相应的权限
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc," +
"/main.html,/insert,/delete"));
}
}
重启项目后,在浏览器中输入账号:admin,密码:123。后可以正确进入到 login.html 页面。
<2>.自定义登录页面
1.修改配置类
修改配置类中主要是设置哪个页面是登录页面。配置类需要继承WebSecurityConfigurerAdapte,并重写 configure方法。
successForwardUrl() //登录成功后跳转地址
loginPage() //登录页面
loginProcessingUrl //登录页面表单提交地址,此地址可以不真实存在。
antMatchers() //匹配内容
permitAll() //允许
<3>.自定义成功处理器
使用successForwardUrl()时表示成功后转发请求到地址。内部是通过 successHandler() 方法进行控制成功后交给哪个类进行处理
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 {
System.out.println(request.getRemoteAddr());
User user = (User) authentication.getPrincipal();
System.out.println(user.getUsername());
//输出null(保密)
System.out.println(user.getPassword());
System.out.println(user.getAuthorities());
response.sendRedirect(url);
}
}
<4>.自定义失败处理器
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);
}
}
<5>.自定义登录配置代码示例
@Override
protected void configure(HttpSecurity http) throws Exception {
//表单提交
http.formLogin()
.usernameParameter("username123")
.passwordParameter("password123")
//当发现/login时认为是登录,必须和表单提交的地址一样,去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/showLogin")
//登录成功后跳转页面,Post请求
.successForwardUrl("/toMain")
//登录成功后处理器,不能和successForwardUrl共存
// .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
//登录失败后跳转页面,Post请求
.failureForwardUrl("/toError");
//登录失败后处理器,不能和failureForwardUrl共存
// .failureHandler(new MyAuthenticationFailureHandler("/error.html"));
}
4.访问控制及角色权限判断
<1>.访问控制URL匹配
在所有匹配规则中取所有规则的交集。配置顺序影响了之后授权效果,越是具体的应该放在前面,越是笼统的应该放到后面
(1).anyRequest()
在之前认证过程中我们就已经使用过 anyRequest(),表示匹配所有的请求。一般情况下此方法都会使用,设置全部内容都需要进行认证。
.anyRequest().authenticated();
(2).antMatcher()
public C antMatchers(String... antPatterns)
参数是不定向参数,每个参数是一个 ant 表达式,用于匹配 URL规则。
规则如下:
? : 匹配一个字符
*:匹配 0 个或多个字符
** :匹配 0 个或多个目录
(3).regexMatchers()
使用正则表达式进行匹配。和 antMatchers() 主要的区别就是参数, antMatchers() 参数是 ant 表达式,regexMatchers() 参数是正则表达式
<2>.内置访问控制方法
1.permitAll()
permitAll()表示所匹配的 URL 任何人都允许访问
2.authenticated()
authenticated()表示所匹配的 URL 都需要被认证才能访问。
3.anonymous()
anonymous()表示可以匿名访问匹配的URL。和permitAll()效果类似,只是设置为 anonymous()的 url 会执行 filter链中
4.denyAll()
denyAll()表示所匹配的5. URL 都不允许被访问。
rememberMe()
被“remember me”的用户允许访问
5.fullyAuthenticated()
如果用户不是被 remember me 的,才可以访问
<3>.角色权限判断
1.hasAuthority(String)
判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建 User 对象时指定的。 admin和normal 就是用户的权限。admin和normal 严格区分大小写
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc," +
"/main.html,/insert,/delete"));
在配置类中通过 hasAuthority(“admin”)设置具有 admin 权限时才能访问。
.antMatchers("/main1.html").hasAuthority("admin")
2.hasAnyAuthority(String …)
如果用户具备给定权限中某一个,就允许访问。下面代码中由于大小写和用户的权限不相同,所以用户无权访问
.antMatchers("/main1.html").hasAnyAuthority("adMin","admiN")
3.hasRole(String)
如果用户具备给定角色就允许访问。否则出现 403。
参数取值来源于自定义登录逻辑 UserDetailsService 实现类中创建 User 对象时给 User 赋予的授权。
在给用户赋予角色时角色需要以: ROLE_开头 ,后面添加角色名称。例如:ROLE_abc 其中 abc 是角色名,ROLE_是固定的字符开头。
使用 hasRole()时参数也只写 abc 即可。否则启动报错。
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc," +
"/main.html,/insert,/delete"));
.antMatchers("/main1.html").hasRole("abc")
4.hasAnyRole(String …)
如果用户具备给定角色的任意一个,就允许被访问
5.hasIpAddress(String)
如果请求是指定的 IP 就运行访问。
可以通过 request.getRemoteAddr() 获取 ip 地址。
需要注意的是在本机进行测试时 localhost 和 127.0.0.1 输出的 ip地址是不一样的。
当浏览器中通过 localhost 进行访问时控制台打印的内容:0:0:0:0:0:0:0:1
角色权限判断代码示例:
//授权认证
http.authorizeRequests()
//error.html不需要被认证
// .antMatchers("/error.html").permitAll()
.antMatchers("/error.html").access("permitAll()")
//login.html不需要被认证
// .antMatchers("/login.html").permitAll()
.antMatchers("/showLogin").access("permitAll()")
.ant以上是关于SpringSecurity Oauth2Authentication对象使用的主要内容,如果未能解决你的问题,请参考以下文章