Spring Security、Cloud OAuth2的实例-用户名和密码模式

OAuth2

是一个开放授权标准,
允许第三方应用访问资源拥有者在资源服务器的特定私有资源。

角色

OAuth2中的角色

Client

第三方应用

Resource Owner

资源拥有者

Resource Server

资源服务器

Authorization Server

授权服务器

维护Client、Resource Owner、Resource Server三者之间的关系。
Resource Server和Authorization Server既可以是分开的两个服务,
也可以是同一个服务。

四种授权的模式

oauth2定义了四种授权的模式(流程),
oauth2的核心就是向第三方应用授权)

用户名密码模式

我们在微服务之中用到了用户名密码模式来进行鉴权
Spring Security、Cloud OAuth2的实例-用户名和密码模式_第1张图片

授权码模式

授权码模式是oauth2中最常用也是最安全的授权模式
Spring Security、Cloud OAuth2的实例-用户名和密码模式_第2张图片

隐藏式授权模式

客户端凭证模式

项目说明

今天我们实现的是用户名密码模式

auth-server

授权服务器

Spring Boot2.3.10

https://start.spring.io/

jdk

Java8

Dependencies

Spring Web、Spring Security、Cloud OAuth2
Spring Security、Cloud OAuth2的实例-用户名和密码模式_第3张图片

user-server

资源服务器,收到请求后会到auth-server验证

Spring Boot2.3.10

https://start.spring.io/

jdk

Java8

Dependencies

Spring Web、Spring Security、Cloud OAuth2
Spring Security、Cloud OAuth2的实例-用户名和密码模式_第4张图片
Spring Security、Cloud OAuth2的实例-用户名和密码模式_第5张图片

配置授权服务器

application.yml

server:
  port: 8080

Spring Security配置

package com.superv.authserver.config;

import org.springframework.context.annotation.Bean;
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 yangwei
 * @describition Spring Security配置
 *
 * @time 2021年5月11日-上午10:57:59
 */
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     

	/**
	 * 密码加密工具
	 * @return
	 */
	@Bean
	public PasswordEncoder passwordEncoder() {
     
		return new BCryptPasswordEncoder();
	}
	
	/**
	 * 用户名密码模式要指定的授权管理Bean
	 */
	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
     
		return super.authenticationManagerBean();
	}

	/**
	 * 允许匿名访问所有接口,否则oauth的/oauth/**接口无法访问
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
     
		// TODO Auto-generated method stub
//		super.configure(http);
		http.authorizeRequests()
			.antMatchers("/**").permitAll();
	}
	
}

实现UserDetailsService

用户(资源拥有者)身份验证

为了方便演示,把资源拥有者的用户名和密码都写在了代码里,
真实情况应该是从数据库查询。

package com.superv.authserver.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;
import org.springframework.stereotype.Component;

/**
 * @author yangwei
 * @describition UserDetailsService实现
 *
 * @time 2021年5月11日-上午
 */
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
     

	@Autowired
	public PasswordEncoder passwordEncoder;
	
	/**
	 * 用户身份验证
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
     
		// TODO Auto-generated method stub
//		return null;
		if (!"admin".equalsIgnoreCase(username)) {
     
			throw new UsernameNotFoundException(username);
		} else {
     
			// 角色
			String role = "ROLE_ADMIN";
			List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
			authorities.add(new SimpleGrantedAuthority(role));
			
			String password = passwordEncoder.encode("123456");
			return new org.springframework.security.core.userdetails.User(username, password, authorities);
		}
	}

}

Cloud OAuth2配置

为了方便演示,把客户端(资源服务器)信息都写在了代码里,
真实情况应该是从数据库查询

package com.superv.authserver.config;

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.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;
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;

/**
 * @author yangwei
 * @describition Cloud OAuth2配置
 *
 * @time 2021年5月11日-上午
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
     
	
	@Autowired
	public TokenStore tokenStore;

	@Autowired
	public PasswordEncoder passwordEncoder;
	
	@Autowired
	public UserDetailsService userDetailsService;
	
	@Autowired
	public AuthenticationManager authenticationManager;
	
	/**
	 * 基于内存的TokenStore
	 * @return
	 */
	@Bean
	public TokenStore tokenStore() {
     
		return new InMemoryTokenStore();
	}

	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
     
		// TODO Auto-generated method stub
//		super.configure(security);
		security.allowFormAuthenticationForClients(); // 允许客户端即资源服务器访问oauth2授权接口
		security.tokenKeyAccess("isAuthenticated()"); // 允许已授权用户访问获取token接口
		security.checkTokenAccess("isAuthenticated()"); // 允许已授权用户访问验证token接口
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
     
		// TODO Auto-generated method stub
//		super.configure(clients);
		clients.inMemory()
			.withClient("user-client") // 资源服务器
			.secret(passwordEncoder.encode("user-secret-8888"))
			.authorizedGrantTypes("refresh_token", "authorization_code", "password") // 用户名密码模式
			.accessTokenValiditySeconds(3600) // token有效时间:秒
			.scopes("all");
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
     
		// TODO Auto-generated method stub
//		super.configure(endpoints);
		endpoints.authenticationManager(authenticationManager) // 支持用户名密码模式
			.userDetailsService(userDetailsService) // 用户验证服务
			.tokenStore(tokenStore); // token存储方式
	}
	
}

配置资源服务器

application.yml

server:
  port: 8081

security:
  oauth2:
    client:
      client-id: user-client
      client-secret: user-secret-8888
#      access-token-uri: http://localhost:8080/oauth/token
#      user-authorization-uri: http://localhost:8080/oauth/authorize
    resource:
      id: user-client
      user-info-uri: user-info
    authorization:
      check-token-access: http://localhost:8080/oauth/check_token

ResourceServer配置

package com.superv.userserver.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;

/**
 * @author yangwei
 * @describition ResourceServer配置
 *
 * @time 2021年5月11日-下午5:01:27
 */
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
     

	@Value("${security.oauth2.client.client-id}")
	private String clientId;
	
	@Value("${security.oauth2.client.client-secret}")
	private String clientSecret;
	
	@Value("${security.oauth2.authorization.check-token-access}")
	private String checkTokenEndpointUrl;
	
	@Bean
	public RemoteTokenServices tokenService() {
     
		RemoteTokenServices tokenService = new RemoteTokenServices();
		tokenService.setClientId(clientId);
		tokenService.setClientSecret(clientSecret);
		tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
		return tokenService;
	}

	@Override
	public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
     
		// TODO Auto-generated method stub
//		super.configure(resources);
		resources.tokenServices(tokenService());
	}
	
}

资源:controller

package com.superv.userserver.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
     

	@RequestMapping("/hello")
	@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
	public String hello() {
     
		return "hello";
	}
	
}

验证

分别启动授权服务器auth-server、资源服务器user-server,postman。

请求授权服务器获取access_token

POST:http://localhost:8080/oauth/token?grant_type=password&username=admin&password=123456&scope=all

请求header中添加Authorization,
值为:Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==
注意:Basic后跟有一个空格

Basic+" "后的内容为"user-client:user-secret-8888"经过base64编码后的值
Spring Security、Cloud OAuth2的实例-用户名和密码模式_第6张图片
access_token的值,等会要用到

携带access_token访问资源服务器

GET:http://localhost:8081/user/hello

请求header中添加Authorization,
值为:bearer 1e48fb5d-ed96-467b-a592-b8a449d9d128
注意:bearer后跟有一个空格,bearer是固定写法为token的类型

bearer+" "后的内容为上一步获取到的access_token
Spring Security、Cloud OAuth2的实例-用户名和密码模式_第7张图片
返回结果正常:hello

过程

如果不携带access_token访问会怎么样?你会看到这样的结果:

{
     
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

这中间又发生了哪些不为人知的事情呢?
Spring Security、Cloud OAuth2的实例-用户名和密码模式_第8张图片
当我们通过客户端-postman访问资源服务器时,
OAuth2AuthenticationProcessingFilter拦截token,
我也不知道对应图中的哪一个。。
然后会调用RemoteTokenServices,
去授权服务器http://localhost:8080/oauth/check_token验证token,
把无状态的token转化成用户信息。

不当之处,请予指正。

参考文章:

架构师girl:可能是全网最详细的 Spring Cloud OAuth2 单点登录使用教程
MacroZheng:Spring Cloud Security:Oauth2使用入门
LightOfMiracle:Spring Boot整合oauth2.0搭建统一授权服务

你可能感兴趣的:(SpringCloud,spring,boot,oauth2)