1.架构
OAuth 2.0 provider是负责公开暴露的资源的,provider通过管理和验证用于访问受保护资源的OAuth 2.0令牌。授权服务和资源服务在逻辑上是分开的,但是可以在一台服务器中,多个资源服务器也可以共享一台授权服务器,
AuthorizationEndpoint
用于服务于授权请求。默认网址:/oauth/authorize
。TokenEndpoint
用于服务访问令牌的请求。默认网址:/oauth/token
。
要实现资源服务器,需要走过滤器:OAuth2AuthenticationProcessingFilter
用于加载给定的认证访问令牌请求的认证。
授权服务器的配置:
ClientDetailsServiceConfigurer
:定义客户端详细信息服务的配置程序。可以初始化客户端详细信息,或者您可以仅引用现有商店。AuthorizationServerSecurityConfigurer
:定义令牌端点上的安全约束。AuthorizationServerEndpointsConfigurer
:定义授权和令牌端点以及令牌服务。
第一步首先就是要把授权服务器配置好,配置好了之后就可以拿到Token了。因为使用新版,遇到很多的坑,所以有必要做一个详细的记录。主要给出配置,如何连数据库的操作与Security最新版一样。
父Maven依赖:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wx</groupId>
<artifactId>security-oauth</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modules>
<module>user-service</module>
<module>uaa-service</module>
<module>eureka-server</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
授权服务器的依赖:
<?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>com.wx</groupId>
<artifactId>security-oauth</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wx</groupId>
<artifactId>uaa-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>uaa-service</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</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>
<!--通用Mapper配置-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
授权服务器的Oauth2 Server配置:
package com.wx.uaaservice.config;
import com.wx.uaaservice.service.impl.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
/**
* Created by IntelliJ IDEA.
* User: wangxiang
* Date: 2019/9/21
* To change this template use File | Settings | File Templates.
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// @Autowired
// @Qualifier("dataSource")
// private DataSource dataSource;
private TokenStore tokenStore = new InMemoryTokenStore();
// JdbcTokenStore tokenStore = new JdbcTokenStore(dataSource);
/**
* 配置授权的Token节点和Token服务,用来配置令牌端点(Token Endpoint)的安全约束.
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许表单认证
// oauthServer.allowFormAuthenticationForClients();
// oauthServer.checkTokenAccess("permitAll()");
security
// .realm("user-service") //安全域的配置
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
/**
* 用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,
* 能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
* clientId:(必须的)用来标识客户的Id。
* secret:(需要值得信任的客户端)客户端安全码,如果有的话。
* redirectUris 返回地址,可以理解成登录后的返回地址,可以多个。
* 应用场景有:客户端swagger调用服务端的登录页面,登录成功,返回客户端swagger页面
* authorizedGrantTypes:此客户端可以使用的权限(基于Spring Security authorities)
* authorization_code:授权码类型、implicit:隐式授权类型、password:资源所有者(即用户)密码类型、
* client_credentials:客户端凭据(客户端ID以及Key)类型、refresh_token:通过以上授权获得的刷新令牌来获取新的令牌。
* scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。
* accessTokenValiditySeconds token有效时长
* refreshTokenValiditySeconds refresh_token有效时长
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//将客户端的信息配置在内存当中
clients.inMemory()
.withClient("user-service")
.secret(new BCryptPasswordEncoder().encode("123456"))
.redirectUris("")
// .authorizedGrantTypes("authorization_code", "client_credentials", "password", "refresh_token")
.authorizedGrantTypes("password")
.scopes("server")
// .resourceIds("user-service")
.accessTokenValiditySeconds(60 * 60)
.refreshTokenValiditySeconds(60 * 60);
//授权码模式
// clients.inMemory()
// .withClient("client_3")
// .authorities("authorization_code", "refresh_token")
// .secret(new BCryptPasswordEncoder().encode("123456"))
// .authorizedGrantTypes("authorization_code")
// .scopes("all", "read", "write")
// .accessTokenValiditySeconds(7200)
// .refreshTokenValiditySeconds(10000)
// .redirectUris("http://localhost:8080/callback", "http://localhost:8080/signin")
// //密码模式
// .and().withClient("user-service")
// .authorizedGrantTypes("password", "refresh_token")
// .scopes("all", "read", "write")
// .accessTokenValiditySeconds(7200)
// .refreshTokenValiditySeconds(10000)
// .authorities("password")
// .secret(new BCryptPasswordEncoder().encode("123456"));
// clients.inMemory()
// .withClient("demoClient")
// .secret(passwordEncoder.encode("demoSecret"))
// .redirectUris("")
// .authorizedGrantTypes("authorization_code", "client_credentials", "password", "refresh_token")
// .authorities("authorization_code", "refresh_token", "password")
// .scopes("all")
// .resourceIds("oauth2-resource")
// .accessTokenValiditySeconds(60 * 60)
// .refreshTokenValiditySeconds(60 * 60);
}
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
/**
* 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
* 访问地址:/oauth/token
* 属性列表:
* authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,
* 需要设置为这个属性注入一个 AuthenticationManager 对象。
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore) //Token的存储方式,采用的方式是将Token储存在内存当中
.authenticationManager(authenticationManager) //设置认证管理器,密码认证配置,只有配置了该选项密码才会开启认证
.userDetailsService(userDetailsService) //配置获取用户认证信息接口, //refresh_token 需要 UserDetailsService is required
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); //设置访问/oauth/token接口,获取token的方式
}
}
WebSecurityConfig配置:
package com.wx.uaaservice.config;
import com.wx.uaaservice.service.impl.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
/**
* Created by IntelliJ IDEA.
* User: wangxiang
* Date: 2019/9/21
* To change this template use File | Settings | File Templates.
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
/**
* 配置认证用户的获取来源
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication()
// .passwordEncoder(new BCryptPasswordEncoder())
// .withUser("admin")
// .password(new BCryptPasswordEncoder().encode("admin"))
// .roles("USER");
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
/**
* 配置了验证管理的Bean
*
* @return
* @throws Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 新版中必须要注入一个实例进去,不然会报id为null
*
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
有几点要注意的,用户的信息需要加密,客户端信息获取需要对密钥加密,必须要注入一个加密器的实例,这些没做好统统验证id会为null,其次是注入验证bean的时候名字别乱写,因为他是对父类的覆盖,不然会循环注入导致栈溢出的错误。
ok,这些都做好了以后,我们就可以来测试获取token以及来验证token的正确性了
首先获取token:
curl user-service:123456@localhost:8001/oauth/token -d grant_type=password -d username=admin -d password=admin
然后再验证token:
OK,接下来配置资源服务器,
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>com.wx</groupId>
<artifactId>security-oauth</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wx</groupId>
<artifactId>user-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>user-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--<!– springboot2.0已经将oauth2.0与security整合在一起 –>-->
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-security</artifactId>-->
<!--</dependency>-->
<!--<!– 由于一些注解和API从spring security5.0中移除,所以需要导入下面的依赖包 –>-->
<!--<dependency>-->
<!--<groupId>org.springframework.security.oauth.boot</groupId>-->
<!--<artifactId>spring-security-oauth2-autoconfigure</artifactId>-->
<!--<version>2.0.0.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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--feign,feign的版本必须要指定-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<!--通用Mapper配置-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
spring:
application:
name: user-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/sys-user?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
username: root
password: 133309
mybatis:
mapper-locations: classpath:mapper/*
type-aliases-package: com.wx.uaaservice.domain.*
mapper:
mappers: com.wx.userservice.utils.IBaseDao
identity: MYSQL
server:
port: 8002
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8000/eureka/
security:
oauth2:
client:
client-id: user-service
client-secret: 123456
access-token-uri: http://localhost:8001/oauth/token
user-authorization-uri: http://localhost:8001/oauth/authorize
resource:
token-info-uri: http://localhost:8001/oauth/check_token
安全配置:
package com.wx.userservice.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
// @Override
// public void configure(HttpSecurity http) throws Exception {
//
// http.authorizeRequests()
// .antMatchers("/user/registry").permitAll()
// .anyRequest().authenticated();
// }
@Override
public void configure(HttpSecurity http) throws Exception {
//表单登录 方式
// http.formLogin()
// .loginPage("/authentication/require")
// //登录需要经过的url请求
// .loginProcessingUrl("/authentication/form")
// .successHandler(myAuthenticationSuccessHandler)
// .failureHandler(myAuthenticationFailHandler);
http
.authorizeRequests()
.antMatchers("/user/*")
.authenticated()
.antMatchers("/oauth/token").permitAll()
.anyRequest()
.permitAll()
.and()
//关闭跨站请求防护
.csrf().disable();
}
}
其余的用户的信息获取等等配置和最新版Security 工程一样。
ok,现在我们来测试获取token,然后访问需要权限和不要权限的接口。
获取Token:
访问不需要权限的接口:
加上权限,访问需要权限的接口:
github:https://github.com/WangAlainDelon/security-oauth.git