Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2

1.为什么微服务要引入Oauth2.0

     关于了解Oauth2.0是什么,阮一峰老师的http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html已经详细地分析了Oauth2.0。在Spring Boot+Spring Cloud的微服务架构中,如果每个服务都维护一套自己的鉴权逻辑,这样不同的服务之间可能无法协同运作,因此出现了Spring Security Oauth2.0:通过鉴权服务提供acess_token,其它微服务作为资源服务通过acess_token调用受保护的接口。本文中鉴权服务器和资源服务器是在一起的,首先通过自制的登录页面进行登录,登录成功后再通过Spring Security的组件预留的api进行令牌模式或密码模式获取acess_token,通过携带acess_token调用受保护的接口。

2.流程图

常用的2种授权模式

       授权码模式,首先要经过授权获取对应的授权code,然后再通过token接口获取access_token,用于第三方去请求某个接口的时候多用这种模式,如微信的API调用。

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第1张图片

        密码模式,直接通过密码和用户名token接口获取access_token,在我负责过的功能当中,一般该模式适用于内部服务之间的调用。

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第2张图片

3.引入依赖及配置

Pom.xml依赖

        
        
            org.springframework.cloud
            spring-cloud-starter-oauth2
        
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

添加application.yml设置登录静态页面的位置

spring:
  thymeleaf:
    prefix: classpath:/templates/
 

   该配置类添加@EnableAuthorizationServer注解使该模块服务成为一个鉴权服务,他提供了/oauth/authorize,/oauth/token,/oauth/check_token,/oauth/confirm_access,/oauth/error,token相关的预设API接口,其中在configure方法中.inMemory()设置该了该服务的相关信息保存在浏览器内存中,.withClient("demoApp")设置了该鉴权服务的id,.secret("demoAppSecret")设置了鉴权密码,.authorizedGrantTypes("authorization_code", "password","refresh_token") 设置了服务支持的授权方法 

package com.security.oauth2test.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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;

@Configuration
@EnableAuthorizationServer //提供/oauth/authorize,/oauth/token,/oauth/check_token,/oauth/confirm_access,/oauth/error
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()//数据存在内存中
                .withClient("demoApp")//授权服务器id
                .secret("demoAppSecret")//授权密码
                .authorizedGrantTypes("authorization_code", "password", "refresh_token")//获取模式
                .scopes("all")
                .resourceIds("oauth2-resource")//资源服务器id
                .accessTokenValiditySeconds(1200)//token的存在时间
                .refreshTokenValiditySeconds(50000);//刷新token的token的存在时间
    }
}

 

4.效果

       无论是授权码模式还是密码模式,首先要登录系统,形成登录成功的浏览器SESSION,下面源码分析会解析为什么需要登录才能继续进行token的获取。

授权码模式

client_id:这是客户端id,在本文中在配置类中设置了该值为demoApp

response_type: 表示授权模式,在授权码模式中,该值为code

redirect_uri:需要跳转的url

        在授权码模式中,首先要向客户端进行请求授权,当我们带上参数访问/oauth/authorize并且点击授权后就能获取授权的code值,本文中该值为olfUVS

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第3张图片

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第4张图片

client_id:在本文中在配置类中设置了该值为demoApp

client_secret:在本文中在配置类中定义为demoAppSecret

code:上个url反馈的值

grant_type:使用授权码模式,故这个值为authorization_code

redirect_uri:需要跳转的url

       获得授权之后,可以通过code值,和预先约定的信息,通过访问/oauth/token获取访问资源的access_token,要注意的是使用授权码模式进行token获取的时候跳转的url必须两次请求的时候都是一样的。

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第5张图片

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第6张图片

 

密码模式

client_id:在本文中在配置类中设置了该值为demoApp

client_secret:在本文中在配置类中定义为demoAppSecret

grant_type:使用密码模式,故这个值为password

username:获取token的用户名

password:获取token的用户对应的密码

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第7张图片

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第8张图片

无论是授权码模式还是密码模式,当他们访问端口生成token,使用返回的token:e701a724-a55a-4c6c-8cd2-0498c98d409e 成功调用的接口

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第9张图片

无效的token调用失败

Spring Security从单体应用到分布式(三)-引入Spring Security Oauth2_第10张图片

5.源码分析

       在令牌模式和密码模式的源码中,!(principal instanceof Authentication)要求了用户必须进行登录才能进行操作,反过来看因为如果没有通过身份认证,就不能通过该身份生成对应的access_token

	@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
	public ResponseEntity postAccessToken(Principal principal, @RequestParam
	Map parameters) throws HttpRequestMethodNotSupportedException {

		if (!(principal instanceof Authentication)) {
			throw new InsufficientAuthenticationException(
					"There is no client authentication. Try adding an appropriate authentication filter.");
		}
    部分省略........
}
	@RequestMapping(value = "/oauth/authorize")
	public ModelAndView authorize(Map model, @RequestParam Map parameters,
			SessionStatus sessionStatus, Principal principal) {

		try {

			if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
				throw new InsufficientAuthenticationException(
						"User must be authenticated with Spring Security before authorization can be completed.");
			}
     部分省略........

}

       在TokenEndpoint中,token获取接口/oauth/token是写有GET和POST方法的,但是在默认的配置下只允许使用POST做请求方式。

	@RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
	public ResponseEntity getAccessToken(Principal principal, @RequestParam
	Map parameters) throws HttpRequestMethodNotSupportedException {
		if (!allowedRequestMethods.contains(HttpMethod.GET)) {
			throw new HttpRequestMethodNotSupportedException("GET");
		}
		return postAccessToken(principal, parameters);
	}
	
	@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
	public ResponseEntity postAccessToken(Principal principal, @RequestParam
	Map parameters) throws HttpRequestMethodNotSupportedException {}

     如果需要让/oauth/token支持GET方法就必须在初始化的时候给它多添加请求的方法方式,只需要在allowedTokenEndpointRequestMethods方法中多添加请求类型就可以了

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(customAuthenticationManager());
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
    }

github:https://github.com/tale2009/spring-security-learning/tree/master/oauth2

总结

        到这里,关于在分布式系统上使用Spring Security的前期知识已经通过三篇博文讲完,基于Spring家族的关系,Spring Security变得非常契合用在Spring Boot+Spring Cloud架构下,后面我会发布关于Spring Boot+Spring Cloud架构下如何使用Spring Security的文章。

你可能感兴趣的:(权限相关)