一、OAUTH2.0简单介绍

OAuth2.0(开放授权)是一个开放标准,**允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不 需要将用户名和密码及其他敏感信息提供给第三方(客户端)应用。

OAuth2.0有四种授权模式:授权码模式,简化模式,密码式,客户端凭证。

OAuth授权过程涉及这几个角色

认证服务,资源服务 这两个通常都是同一个Oauth服务提供商的

客户端,

用户

我们以微信来举例,微信提供了Oauth认证服务,我们的头像和昵称等信息作为用户资源存储在资源服务中,

假如我们注册的另一个平台例如博客园支持微信登录,这时候博客园就是客户端,它需要用户的授权来访问用户存在微信服务中的头像和昵称等信息,这就是一个经典的Oauth授权场景。

用户通过认证服务(微信服务)给客户端(博客园)授权,使其能访问存储在资源服务器中的资源(头像和昵称)

下面先用spring security搭建一个授权服务再逐个对这几种模式进行讲解。

二、搭建授权服务

创建一个springboot工程,然后导入spring security相关的依赖

2.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>oauth_study2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>1.8</java.version>
    </properties>

    <!--   这个parent很重要     -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <dependencies>

        
        <!--   web工程依赖     -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- 重要 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <!-- 重要 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!--     springcloud版本指定       -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.0</version>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.1.3.RELEASE</version>
            </dependency>

            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.10.5.1</version>
            </dependency>

            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-core</artifactId>
                <version>2.10.5</version>
            </dependency>

            <dependency>
                <groupId>org.springframework.security.oauth.boot</groupId>
                <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                <version>2.1.3.RELEASE</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

2.2 启动类

在启动类上开启OAUTH授权服务

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

@SpringBootApplication
@EnableAuthorizationServer
public class UaaApplication {

    public static void main(String[] args) {
        SpringApplication.run(UaaApplication.class);
    }
}

2.3 配置文件

在配置文件中指定服务的端口和根路径

spring.application.name=uaa-service
server.port=53020
server.servlet.context-path = /uaa

2.4 spring security配置

这个授权服务本质上还是一个spring security应用,所以需要进行spring security的配置,

需要配置用户信息,安全配置,密码编码器,认证管理器。采用继承WebSecurityConfigurerAdapter 的方式来进行配置

//spring security安全配置,主要配置用户信息,也就是我们这个认证服务器保护的是哪些用户的资源
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 在内存中配置两个用户,授权服务保护的就是用户的资源
    @Bean
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("lyy").password("123").authorities("ROLE_p1").build());
        manager.createUser(User.withUsername("zs").password("123").authorities("ROLE_ADMIN").build());
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().anyRequest().permitAll()
                .and().formLogin();//开启表单登录

    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        // 密码编码器,spring security中密码必须进行编码,而我们配置的用户密码是明文,
        // 所以这里使用一个不进行编码的编码器
        return NoOpPasswordEncoder.getInstance();
    }

    // 给spring容器中注入认证管理器,后续配置认证管理器的时候会使用到
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

2.5 授权服务功能相关配置

通过继承AuthorizationServerConfigurerAdapter来进行配置

要让这个服务具备授权服务器的功能,需要利用spring security提供的oauth2功能,需要配置以下信息

(1) 客户端信息 (2) 令牌的访问端点 (3) 令牌端点的安全约束,这正好对应了

AuthorizationServerConfigurerAdapter中的几个方法

public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
    public AuthorizationServerConfigurerAdapter() {
    }

    // 令牌端点的安全约束
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    }
	// 客户端信息配置
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    }
	// 令牌端点配置
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    }
}

所以需要定义一个配置类继承这个类重写这三个方法进行配置,

先简单把client,授权码,token的存储都配置在内存中,token使用普通的token来快速实现一个授权服务

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private ClientDetailsService clientDetailsService;

    //配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("c1")//客户端名称
                .secret(NoOpPasswordEncoder.getInstance().encode("secret"))//客户端密钥,需要配置密文
                .resourceIds("res1")
                .authorizedGrantTypes("authorization_code",
                        "password", "client_credentials", "implicit", "refresh_token")//此客户端允许的授权类型
                .scopes("all")//权限范围,这只是一个标识,资源服务验证的时候会用到
                .autoApprove(false)//不自动授权,意思就是需要跳转到认证页面
                .redirectUris("http://www.baidu.com");//回调地址,用户完成授权后通过这个地址传递授权码或者token
    }

    //配置令牌的访问端点:需要配置认证管理器,授权码服务,令牌管理服务,令牌的请求方式
    //这几个都需要以bean的形式进行配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)//认证管理器,密码模式时使用
                .authorizationCodeServices(authorizationCodeServices)//授权码服务,授权码模式时使用
                .tokenServices(tokenService())//令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许以POST方式获取令牌
    }

    //配置令牌端点的安全约束
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")//完全公开
                .checkTokenAccess("permitAll()")//完全公开
                .allowFormAuthenticationForClients();//允许表单认证
    }

    //配置授权码服务bean
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        //授权码保存在内存中
        return new InMemoryAuthorizationCodeServices();
    }

    //配置令牌管理服务
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(clientDetailsService);//客户端详情,从容器中获取
        tokenServices.setTokenStore(new InMemoryTokenStore());//token的存储方式,保存在内存中
        tokenServices.setSupportRefreshToken(true);//支持刷新令牌
        tokenServices.setAccessTokenValiditySeconds(7200);//token的有效期
        tokenServices.setRefreshTokenValiditySeconds(7200);//刷新token的有效期
        return tokenServices;
    }
}

这样一个简单的授权服务就配置好了。

2.6 jwt token的配置

这一部分是和3.3的内容配和的可以先跳过,看到3.3时在回过头来看

上边的配置中使用的是普通token,资源服务验证token时需要远程请求认证服务才能验证。这一节将配置认证服务生成jwt token,资源服务可以自己验证jwt token

jwt令牌由三部分组成,header,payload,Signature

header: 头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)

{
"alg": "HS256",
"typ": "JWT"
}

第一部分是把这个字符串用base64编码

payload: 这个令牌携带的用户信息 ,

{
"sub": "1234567890",
"name": "456",
"admin": true
}

第二部分是把这个字符串用base64编码

Signature : 签名, 第三部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明 签名算法进行签名

具体的配置

tokenStore使用JwtTokenStore ,并配置TokenConverter

以下是授权服务的配置类,和使用普通token的区别是:

(1) 使用JwtTokenStore

(2) 在tokenservice中配置了TokenEnhancer对生成的token进行增强使其变成jwt token

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private ClientDetailsService clientDetailsService;


    //配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("c1")//客户端名称
                .secret(NoOpPasswordEncoder.getInstance().encode("secret"))//客户端密钥,需要配置密文
                .resourceIds("res1")
                .authorizedGrantTypes("authorization_code",
                        "password", "client_credentials", "implicit", "refresh_token")//此客户端允许的授权类型
                .scopes("all")//权限范围,这只是一个标识,资源服务验证的时候会用到
                .autoApprove(false)//不自动授权,意思就是需要跳转到认证页面
                .redirectUris("http://www.baidu.com");//回调地址,用户完成授权后通过这个地址传递授权码或者token
//                .and()
//                .withClient("c2")//客户端名称2
//                .secret(NoOpPasswordEncoder.getInstance().encode("secret"))//客户端密钥,需要配置密文
//                .resourceIds("res1")
//                .authorizedGrantTypes("authorization_code",
//                        "password", "client_credentials", "implicit", "refresh_token")//此客户端允许的授权类型
//                .scopes("all")//权限范围,这只是一个标识,资源服务验证的时候会用到
//                .autoApprove(false)//不自动授权,意思就是需要跳转到认证页面
//                .redirectUris("http://www.baidu.com");//回调地址,用户完成授权后通过这个地址传递授权码或者token

    }

    //配置令牌的访问端点:需要配置认证管理器,授权码服务,令牌管理服务,令牌的请求方式
    //这几个都需要以bean的形式进行配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)//认证管理器,密码模式时使用
                .authorizationCodeServices(authorizationCodeServices())//授权码服务,授权码模式时使用
                .tokenServices(tokenServices())//令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许以POST方式获取令牌
    }

    //配置令牌管理服务
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(clientDetailsService);//客户端详情,从容器中获取
        tokenServices.setTokenStore(tokenStore());//JwtTokenStore
        tokenServices.setSupportRefreshToken(true);//支持刷新令牌
        tokenServices.setAccessTokenValiditySeconds(7200);//token的有效期
        tokenServices.setRefreshTokenValiditySeconds(7200);//刷新token的有效期

        //jwt 令牌相关的配置,配置token的增强器
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenConverter()));
        tokenServices.setTokenEnhancer(tokenEnhancerChain);//配置了token的增强

        return tokenServices;
    }

    //配置令牌端点的安全约束

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")//完全公开
                .checkTokenAccess("permitAll()")//完全公开
                .allowFormAuthenticationForClients();//允许表单认证
    }

    //配置授权码服务bean
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        //授权码保存在内存中
        return new InMemoryAuthorizationCodeServices();
    }

    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(tokenConverter());
    }

    @Bean //注意这个convert一定要配置成bean,否则访问资源服务时会报 Cannot convert access token to JSON
    public JwtAccessTokenConverter tokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");//对称密钥,需要和资源服务器中的tokenConverter保持一致
        return converter;
    }

}

配置完成后测试下生成token

可以看到jwt token的长度要大于普通token

三、授权模式讲解测试

3.1 授权码模式

以用户使用微信账号登录博客园的流程来示意,授权码模式的流程是这样的

第一步请求授权码我们搭建的这个授权服务,请求授权码的地址是这样的

GET http://localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com

在浏览器中访问这个地址,需要传递这几个参数

client_id: 客户端id

response_type:响应类型,这里是在请求授权码所以传code

scope:范围标识,

redirect_uri:重定向url,用户完成授权后授权服务会把用户重定向到这个url

打开浏览器访问上边这个地址,授权服务就会重定向用户到登录页面

这其实就是spring security提供的一个登录页面,这个页面也支持自定义的,这里先使用默认页面进行测试。用户在这个页面输入账号密码进行登录后授权服务就会引导用户到授权页面

这是spring security提供的一个授权页面,用户在这里点同意进行授权就会重定向用户到客户端指定的url并携带授权码,我这里指定的redirect_uri是百度,所以就会跳转到百度

注意看在地址栏的最后携带了授权服务生成的授权码。

第二步客户端拿着授权码后台去授权服务请求token,请求url

POST
http://localhost:53020/uaa/oauth/token

以表单提交的方式(Content-Type: application/x-www-form-urlencoded )提交如下参数

client_id:

client_secret

grant_type:授权类型,和配置客户端时指定的类型要匹配,授权码模式传authorization_code

code

redirect_uri:重定向url要和上一步的保持一致

scope:范围标识

在返回值里就会包含access_token和refresh_token,然后客户端就可以拿着这个token去资源服务器请求用户资源,资源服务器验证token有效后就会允许访问。

授权码模式是这几种模式种最安全的一种模式

3.2 简化模式

简化模式和授权模式的区别就是做了简化,使用这种模式用户完成授权后重定向用户到客户端指定的url时就会携带token,不需要再次去请求token

请求url

GET
http://localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com

请求的时候需要携带client_id,response_type,scope,redirect_uri

注意这种模式需要直接返回token所以response_type=token

用户在浏览器访问这个地址,授权服务会先重定向用户到登录页面,登录成功后会重定向用户到授权页面,完成授权后就会重定向用户到客户端指定的url并携带token

注意看这里的url上携带了 access_token,客户端就可以拿着这个token去资源服务请求资源。

这种模式适用于一些没有后端的应用,可以直接在前端来接收token

3.3 密码模式

这种模式需要用户给客户端提供自己的账号根密码,这种模式适用与授权服务和客户端是同一公司开发完全信任的情况。

首选用户在客户端的登录页面输入自己的账号和密码,然后客户端后台拿着用户的账号密码去授权服务申请token

客户端请求token的地址

POST
http://localhost:53020/uaa/oauth/token

以Content-Type: application/x-www-form-urlencoded 的方式提交如下参数

client_id:

client_secret:

grant_type:授权类型,密码模式传 password

username:

password:

授权服务返回值里就会有access_token和refresh_token

3.4 客户端模式

客户端向授权服务发送自己的账号密码并请求token,这种模式是最方便但最不安全的一种,因为客户端申请token的过程和用户无关,所以需要用户对客户端完全信息。

请求url

POST
http://localhost:53020/uaa/oauth/token

需要传递的参数

client_id:

client_secret:

grant_type:client_credentials

三、搭建资源服务

接下来搭建一个简单的资源服务来测试下客户端使用token去请求资源的情况。

创建一个maven工程,引入的依赖和授权服务的依赖一样。

在启动类上加上@EnableResourceServer注解

@SpringBootApplication
@EnableResourceServer
public class ResourceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ResourceApplication.class);
    }
}

在配置文件中指定应用的端口和路径

spring.application.name=resource-service
server.port=53021
server.servlet.context-path = /resource

配置对哪些资源进行保护

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().antMatchers("/r1/**").authenticated();
        http.authorizeRequests().anyRequest().permitAll()
                .and().formLogin();//开启表单登录

    }

}

利用ResourceServerConfigurerAdapter对资源服务进行配置,配置如何验证token

3.1远程验证token

配置资源服务远程调用授权服务的token校验接口进行验证


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;

@Configuration
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {


    // 先配置资源服务远程调用授权服务的接口去验证token

    @Bean
    public ResourceServerTokenServices resourceServerTokenServices(){
        RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        //资源服务自己也是一个客户端,这里配的是自己在授权服务中注册的账号和密码
        //先配置成c1/secret进行测试
        //也可以在授权服务中重新配置一个客户端
        remoteTokenServices.setClientId("c1");
        remoteTokenServices.setClientSecret("secret");
        remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token");
        return remoteTokenServices;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenServices(resourceServerTokenServices()).resourceId("res1").stateless(true);
    }
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //这个配置的意思是所有的资源都要客户端有 all这个scope标识才能访问
        //这个标识和认证服务中给客户端配置的scope是对应的,也就是限制的访问资源服务的客户端的权限
        http.authorizeRequests().antMatchers("/**").access("#oauth2.hasScope('all')")
                .and().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

测试访问资源,在资源服务中新建一个controller进行测试,注意调用时要按照oauth的规范再请求头中携带token,

需要添加这么一个请求头 Authorization:Bearer 7f1acbe1-3170-455c-8d28-c8a8528b67a6

注意这里配置了RemoteTokenServices来请求认证服务验证token,可以看下远程请求的源码

RemoteTokenServices

@Override
	public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {

		MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
		formData.add(tokenName, accessToken);
		HttpHeaders headers = new HttpHeaders();
		headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
		Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);

		if (map.containsKey("error")) {
			if (logger.isDebugEnabled()) {
				logger.debug("check_token returned error: " + map.get("error"));
			}
			throw new InvalidTokenException(accessToken);
		}

		// gh-838
		if (map.containsKey("active") && !"true".equals(String.valueOf(map.get("active")))) {
			logger.debug("check_token returned active attribute: " + map.get("active"));
			throw new InvalidTokenException(accessToken);
		}

		return tokenConverter.extractAuthentication(map);
	}

	private String getAuthorizationHeader(String clientId, String clientSecret) {

		if(clientId == null || clientSecret == null) {
			logger.warn("Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.");
		}

		String creds = String.format("%s:%s", clientId, clientSecret);
		try {
			return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
		}
		catch (UnsupportedEncodingException e) {
			throw new IllegalStateException("Could not convert String");
		}
	}

这个loadAuthentication方法就是在请求认证服务器,注意看下它调用接口前的参数构造,给请求头中添加了一个

Authorization,值是clientId,clientSecret base64编码的结果,使用Basic认证的方式传递给了认证服务。所以认证服务中会有处理Basic认证的过滤器拦截到这个请求头进行处理,如果传递的client或者密码是错的就会返回401。

但如果你用postman直接请求这个 oauth/check_token不加任何请求头发现是可以通的,这是为什么呢?

我在配置认证服务中令牌端点的安全约束时配置的验证token的端点的安全约束是checkTokenAccess("permitAll()")//完全公开,所以这个验证token的链接是没有权限限制的,但如果你在请求头中携带了Authorization就会被Basic认证的过滤器拦截到所以会进行校验。

3.2 配置资源服务验证 jwt token

这个配置的方法和认证服务类似

(1) 配置tokenstore和JwtAccessTokenConverter

(2) 配置资源服务使用tokenstore不使用 tokenservice

@Configuration
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {


    //配置token的存储策略,使用jwt token
    //@Bean//测试时发现这个不配置成bean也是可以正常验证token的,但最好配置上
    public JwtTokenStore tokenStore(){
        return new JwtTokenStore(tokenConverter());
    }

    @Bean//注意这个convert一定要配置成bean,否则访问资源服务时会报 Cannot convert access token to JSON
    public JwtAccessTokenConverter tokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");//注意这个key要和认证服务中的key保持一致
        return converter;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //使用jwt token时这里配置的是tokenStore
        resources.tokenStore(tokenStore()).resourceId("res1").stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //这个配置的意思是所有的资源都要客户端有 all这个scope标识才能访问
        //这个标识和认证服务中给客户端配置的scope是对应的,也就是限制的访问资源服务的客户端的权限
        http.authorizeRequests().antMatchers("/**").access("#oauth2.hasScope('all')")
                .and().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

}

这样配置后客户端使用token访问资源服务时资源服务就可以自己验证jwt token

请求头还是和使用远程验证时一样

需要添加这么一个请求头

Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJseXkiLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNjc4OTgzNjQ4LCJhdXRob3JpdGllcyI6WyJST0xFX3AxIl0sImp0aSI6ImVlOWY1Y2ZlLTIwOGUtNDk0Yi1hMTlmLWZjNGVjZDVmNzMxNCIsImNsaWVudF9pZCI6ImMxIn0.Sl5qLMOPzWVZ3bomZOgNBBnQRX_6f6MWzR-tad5yWD4

上边携带的就是jwt token
注意上边配置JwtAccessTokenConverter时备注了必须配置成bean,这是为什么呢?配置成bean就会执行类中的

afterPropertiesSet()方法,在这个方法中给类中的verifier 变量进行了赋值,这个变量就是用来编码的,所以只有执行此方法后这个convert才可以使用,我们也可以不把这个类配置成bean,在给signingKey手动赋值后手动执行这个方法也是可以的。

public JwtAccessTokenConverter tokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");//注意这个key要和认证服务中的key保持一致
        try {
            converter.afterPropertiesSet();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return converter;
    }

就像这样。