【Spring-Security】Re10 Oauth2协议 P1 授权码模式 & 密码模式
一、Oauth2协议:
第三方登录,即忘记本站密码,但是登录界面中提供了一些第三方登录,例如微信、支付宝、QQ、等等,通过第三方授权实现登录
第三方认证技术主要解决的时认证标准,各个平台的登录要遵循统一的接口协议
所以这里采用的方案是Oauth2
资料参考:
https://www.jianshu.com/p/84a4b4a1e833
二、Spring-Security + Oauth2 环境搭建
创建一个空的SpringBoot项目:
删除不必要的文件
导入POM依赖坐标:
<?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.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.zeal4j</groupId> <artifactId>so2</artifactId> <version>0.0.1-SNAPSHOT</version> <name>so2</name> <description>Spring-Security + Oauth2 project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <!--<version>2.2.4.RELEASE</version>--> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> <!--<version>2.2.4.RELEASE</version>--> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <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> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version><!--<version>Greenwich.SR2</version>--> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
用户实体类:
package cn.zeal4j.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; /** * @author Administrator * @file Spring-Security + Oauth2 * @create 2020 09 29 11:37 */ @Data @AllArgsConstructor @NoArgsConstructor public class User implements UserDetails { private String username; private String password; private Collection<GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return null; } @Override public String getUsername() { return null; } @Override public boolean isAccountNonExpired() { return false; } @Override public boolean isAccountNonLocked() { return false; } @Override public boolean isCredentialsNonExpired() { return false; } @Override public boolean isEnabled() { return false; } }
Security安全配置:
package cn.zeal4j.configuration; 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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author Administrator * @file Spring-Security + Oauth2 * @create 2020 09 29 11:35 */ @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // csrf httpSecurity.csrf().disable(); // 登录,退出,授权放行 httpSecurity.authorizeRequests().antMatchers("/login/**","/logout/**","/oauth/**/").permitAll(); // 允许表单登录 httpSecurity.formLogin().permitAll(); // 其余请求权限拦截 httpSecurity.authorizeRequests().anyRequest().authenticated(); } }
授权服务配置:
package cn.zeal4j.configuration; 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; /** * @author Administrator * @file Spring-Security + Oauth2 * @create 2020 09 29 11:48 * @description 授权服务器配置 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired private PasswordEncoder passwordEncoder; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients. inMemory(). withClient("admin"). secret(passwordEncoder.encode("112233")). accessTokenValiditySeconds(3600). // 令牌有效时间 一小时 redirectUris("http://www.baidu.com"). // 授权成功的跳转 scopes("all"). // 所有范围 authorizedGrantTypes("authorization_code"); // 授权类型:授权码模式 } }
资源服务配置:
package cn.zeal4j.configuration; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; /** * @author Administrator * @file Spring-Security + Oauth2 * @create 2020 09 29 11:57 */ @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeRequests(). anyRequest().authenticated(). // 默认任何请求都需要授权 and(). requestMatchers().antMatchers("/user/**"); // 资源匹配规则 } }
登录逻辑:
package cn.zeal4j.service.impl; import cn.zeal4j.domain.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; 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; /** * @author Administrator * @file Spring-Security + Oauth2 * @create 2020 09 29 11:35 */
@Service public class CustomUserDetailsServiceImpl implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { String encode = passwordEncoder.encode("123456"); return new User("admin", encode, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
User资源控制器:
package cn.zeal4j.controller; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * @author Administrator * @file Spring-Security + Oauth2 * @create 2020 09 29 12:01 */ @Controller @RequestMapping("user") public class UserController { /** * 获取当前用户 * @param authentication * @return user/getCurrentUser */ @RequestMapping("/getCurrentUser") @ResponseBody public Object getCurrentUser(Authentication authentication) { return authentication.getPrincipal(); } }
启动访问发现:
二、授权码模式实现:
发现生成的UserDetails实例默认设置的都是False,SpringSecurity一经权限判断,直接触发拦截
所以默认这里都要改成TRUE放行通过
@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; }
再次测试Oauth2地址:
http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
点击选择Approve允许,点击Authorize进行授权
https://www.baidu.com/?code=G6ocWn
这个code就是我们的授权码
现在使用Postman进行测试:
1、先选择认证账号信息输入:
2、然后输入请求参数:
grant_type authorization_code code 你得到的授权码 cient_id admin redirect_uri http://www.baidu.com scope all
请求成功则会返回这个消息JSON
{ "access_token": "4c562cc7-07c4-47b6-9a5b-537c4f4079cd", "token_type": "bearer", "expires_in": 43199, "scope": "all" }
得到了许可Token
接着用Token访问资源获取接口:
http://localhost:8080/user/getCurrentUser
得到我们返回的数据:
{ "username": "admin", "password": "$2a$10$tBlHK4KbNFsE7F7usl6AsODWWBkF4oS8Ak4qkaNbEFAsKykvXgH9i", "authorities": [ { "authority": "admin" } ], "enabled": true, "accountNonLocked": true, "credentialsNonExpired": true, "accountNonExpired": true }
如果令牌是错误的
{ "error": "invalid_token", "error_description": "Invalid access token: 2287c3ab-3dc5-475e-97eb-bdffb2a78b8A" }
三、密码模式授权实现:
package cn.zeal4j.configuration; 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.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; /** * @author Administrator * @file Spring-Security + Oauth2 * @create 2020 09 29 11:48 * @description 授权服务器配置 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired private PasswordEncoder passwordEncoder; @Autowired private AuthenticationManager authenticationManager; @Qualifier("customUserDetailsServiceImpl") @Autowired private UserDetailsService userDetailsService; /** * 使用密码模式需要的配置方法 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints. authenticationManager(authenticationManager). userDetailsService(userDetailsService); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients. inMemory(). withClient("admin"). secret(passwordEncoder.encode("112233")). // accessTokenValiditySeconds(3600). // 令牌有效时间 一小时 redirectUris("http://www.baidu.com"). // 授权成功的跳转 scopes("all"). // 所有范围 // authorizedGrantTypes("authorization_code"); // 授权类型:授权码模式 authorizedGrantTypes("password"); // 授权类型:密码模式 } }
授权管理器Bean的注入:
package cn.zeal4j.configuration; import org.omg.CORBA.PUBLIC_MEMBER; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author Administrator * @file Spring-Security + Oauth2 * @create 2020 09 29 11:35 */ @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity. csrf(). disable(). authorizeRequests(). antMatchers("/login/**","/logout/**","/oauth/**/").permitAll(). anyRequest().authenticated(). and(). formLogin().permitAll(); // // csrf // httpSecurity.csrf().disable(); // // // 登录,退出,授权放行 // httpSecurity.authorizeRequests().antMatchers("/login/**","/logout/**","/oauth/**/").permitAll(); // // // 允许表单登录 // httpSecurity.formLogin().permitAll(); // // // 其余请求权限拦截 // httpSecurity.authorizeRequests().anyRequest().authenticated(); } }
Postman请求的地址和前面的密码不变,但是更改授权参数
{ "access_token": "8e51bdc3-50cd-42bd-b41c-28f06e5eca1f", "token_type": "bearer", "expires_in": 43199, "scope": "all" }
用密码模式的令牌获取用户主体,也能拿到用户主体的信息: