一、简单的Spring Authorization Server示例代码

需要有一定的OAuth2的基础
需要有一定的Spring Security基础

Spring Authorization Server

官方简介:Spring Authorization Server is a framework that provides implementations of the OAuth 2.1 and OpenID Connect 1.0
个人理解为OAuth 2.1 and OpenID Connect 1.0的实现。
关于OAuth2有很多的版本,Spring Authorization Server是现在官网正在维护的版本。这里实现的是OAuth2.1,OAuth2.1里面没有密码模式。

一、简单的Spring Authorization Server示例代码_第1张图片

这里用的版本是0.4.2,jdk8的,1.x的版本需要jdk17

这里可以用很简单的代码就实现OAuth2服务端

代码结构如下:
一、简单的Spring Authorization Server示例代码_第2张图片

添加依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-thymeleafartifactId>
dependency>

<dependency>
    <groupId>org.springframework.securitygroupId>
    <artifactId>spring-security-oauth2-authorization-serverartifactId>
dependency>

<dependency>
    <groupId>cn.hutoolgroupId>
    <artifactId>hutool-coreartifactId>
dependency>

applicalication.yml
就配了一个端口

server:
  port: 7000

logging:
  level:
    org.springframework.security: debug

启动类, 没啥特别的

@SpringBootApplication
public class Oauth2ServerApplication {

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

}

private.key:私钥

MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBKxqzuZocGA9bhUsYCCfsl5cH
6omJ9I554J50tlsDl7AQLf+wX7YYWPdJa2V8o1y55wBqYiFE4JQ4H0sQog8itRS+OqwDKjwbedKo
5KTfUzvGjcFL77CWpU844n8e5A5e1oJbAPYPR/6ccHsPUgSxTAJMabaH3i5PTZ3aXiSjsMuXcg5/
HCKONUopgsb8idWSIvfVf2jM7wYFF5oRux4nlOzOCedQoEOSZQa55DElrFV/nVmi7ExIXH7GVG79
rIq0sDMqViQ6MVuXuFmyqAKd5pGA0f8+6echooKPKZklHGnHDMWmJDlCyoVnJSTti3yEhHaf0lfn
Ao/8qMU+iNX7AgMBAAECggEAZLQKCaQ6+WZ5qybETVUDK06kCBZ3eZorJNK7CPGAZVEREn5IjDR5
hBvtXzNEB0RLNQd+qfdajMPfwZpe0d8KsPdiRwHjZwr/pvtNnYsFgP+tbAe+u83La93mfStnRj1y
WHLQJo1Lug+4Zuok3YnOtHeBw0BhTlfAIMu//XWS+FprVxuBwIB6xvLTtGf5CV230y4DNmemFBuX
93qLFDSG3r/iEZ6U78CzjHjitG0kMfPSQ2TmraUJoel01t9JOONGqtsoitaWR7J5NcwLtrHqYXD5
R9d6E7i5e45Xf8HbrNVF0mgw+JQLkmhcTqWHo0Ws634v1r2HBa0HhYs/s2S3MQKBgQD7C7d43WVg
TprhoU/C6oWQOEAZSdSC8qsqg8CaiO0sv4YMGN7dxOWwuuX3OeXB8EjaL//kjnVyyueb4GF6ugMC
kDmoPg8PFOMP3VOs/0PoXQowC57cWMzzcSVepaHr6GDGl5kPZnhx2TLaPKtTRucPz6/+kWkTQCjz
3kOswTjJzQKBgQDE+v2y5gv4/s7iGfeaGMUB7IWn1G51Ay2pwoI2b60tITcbrhM0t4yxWAKUspoC
x3Qk5QNrUfevyvggBbPk0BL5Zy42HoBKGlh+hwcq8wmzdOGD7wcIvWvITceUxLrjD3/HogRrB6l4
VGsQ4Yq4e+iIc3geC6TDVNz1KMJbaR+25wKBgBOCXJa69dbfJPAl3hHyscB8bpbIgwhOHXknVf9s
ZqoUlDE6eY9YbtUmIRruV+mTZ8X09vjnDT+HfypA7LJh5Dv9w01MzVTJtb+U3pzSFY/oMxN6w7Sx
/fNpNpM9YfD4VRT50P4+Y1vNmkMVdeb52pkC9dVdrYG+ebBB9JZnSad9AoGBAMJoWQU8iGp5yWNb
b4S1l5Jbhlnqjg2MUn/uCaeCNq+IzaPS/P+VfBT3oKxzTQ8bHOTg5awA3Oyx7ItmNXLJbUCa9f/R
wJniQJ6303ovHc7wtzYILbARixPIuAZ611wLyvgTTjr39+lbn8OsZcXH/OrW06ELqtRhqCWJ0bB4
IyXXAoGBAMuMNmblY+iGfShJHWyAYESlFqyhYycfFXpec9Admf1znQ1SIo5VRxaDkMsGZImb5pPD
ZQUxNR5kJNr8rqYt21+kwNn0bteROkjQuP50P3dPcfaZfaYoKSSVEs4bnhzvtWCiHkCqf9jWHV+U
XFkhyPqfh2BZkghtU3MshbEX5IdG

public.key公钥

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSsas7maHBgPW4VLGAgn7JeXB+qJifSO
eeCedLZbA5ewEC3/sF+2GFj3SWtlfKNcuecAamIhROCUOB9LEKIPIrUUvjqsAyo8G3nSqOSk31M7
xo3BS++wlqVPOOJ/HuQOXtaCWwD2D0f+nHB7D1IEsUwCTGm2h94uT02d2l4ko7DLl3IOfxwijjVK
KYLG/InVkiL31X9ozO8GBReaEbseJ5TszgnnUKBDkmUGueQxJaxVf51ZouxMSFx+xlRu/ayKtLAz
KlYkOjFbl7hZsqgCneaRgNH/PunnIaKCjymZJRxpxwzFpiQ5QsqFZyUk7Yt8hIR2n9JX5wKP/KjF
PojV+wIDAQAB

这个秘钥对在官方示例里面是生成并保存在内存里面的,每次都不一样,这个秘钥对是我自己生成的,保存在文件里面的,你也可以用官方的生成秘钥的那段代码。

读取秘钥

import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.IoUtil;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * TODO description
 *
 * @author qiudw
 * @date 5/18/2023
 */
public class SecurityUtils {

	public static PublicKey loadPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
		try (InputStream inputStream = SecurityUtils.class.getClassLoader().getResourceAsStream("public.key")) {
			assert inputStream != null;
			byte[] publicKey = IoUtil.readBytes(inputStream);
			KeyFactory keyfactory = KeyFactory.getInstance("RSA");
			X509EncodedKeySpec encodeRule = new X509EncodedKeySpec(Base64.decode(publicKey));
			return keyfactory.generatePublic(encodeRule);
		}
	}
	public static PrivateKey loadPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
		try (InputStream inputStream = SecurityUtils.class.getClassLoader().getResourceAsStream("private.key")) {
			assert inputStream != null;
			byte[] privateKey = IoUtil.readBytes(inputStream);
			KeyFactory keyfactory = KeyFactory.getInstance("RSA");
			PKCS8EncodedKeySpec encodeRule = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
			return keyfactory.generatePrivate(encodeRule);
		}
	}

}

LoginController
定义了一下登录页面,默认的登录页面需要加在国外的资源,太慢

/**
 * 登录相关的控制器
 *
 * @author qiudw
 * @date 7/11/2023
 */
@Controller
@RequestMapping
public class LoginController {

	/**
	 * 跳转到登录页面
	 *
	 * @return 登录页面的地址
	 */
	@GetMapping("/login")
	public ModelAndView loginPage() {
		return new ModelAndView("login");
	}

	/**
	 * 首页
	 *
	 * @return 首页路径
	 */
	@GetMapping({ "/", "/index" })
	public ModelAndView index() {
		return new ModelAndView("index");
	}

}

登录界面 login.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Logintitle>
head>
<body>
<form th:action="@{/login}" method="post">
    <div>
        异常信息:<span th:text="${session.SPRING_SECURITY_LAST_EXCEPTION}">span>
    div>
  <input type="text" name="username" placeholder="用户名" /> <br/>
  <input type="password" name="password" placeholder="密码" /> <br/>
  <button type="submit">登录button> <br/>
form>
body>
html>

安全配置

import com.demo.oauth2.utils.SecurityUtils;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
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.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;

/**
 * 安全配置
 *
 * @author qiudw
 * @date 7/10/2023
 */
@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

	@Bean
	public PasswordEncoder passwordEncoder() {
		return PasswordEncoderFactories.createDelegatingPasswordEncoder();
	}

	@Bean
	@Order(1)
	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
		OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
		http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
				// Enable OpenID Connect 1.0
				.oidc(Customizer.withDefaults());
		http.exceptionHandling()
				.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
				.and()
				.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
		return http.build();
	}

	@Bean
	@Order(2)
	public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				.antMatchers("/login").permitAll()
				.anyRequest().authenticated()
				.and()
				.formLogin()
				.loginPage("/login")
				.loginProcessingUrl("/login")
				.usernameParameter("username")
				.passwordParameter("password")
				.and()
				.csrf().disable();
		return http.build();
	}

	@Bean
	public UserDetailsService userDetailsService() {
		UserDetails userDetails = User.builder()
				.username("admin")
				.password("{noop}admin")
				.roles("ADMIN")
				.build();
		return new InMemoryUserDetailsManager(userDetails);
	}

	@Bean
	public JWKSource<SecurityContext> jwkSource() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
		RSAPublicKey publicKey = (RSAPublicKey) SecurityUtils.loadPublicKey();
		RSAPrivateKey privateKey = (RSAPrivateKey) SecurityUtils.loadPrivateKey();
		RSAKey rsaKey = new RSAKey.Builder(publicKey)
				.privateKey(privateKey)
				.keyID("key-id")
				.build();
		JWKSet jwkSet = new JWKSet(rsaKey);
		return new ImmutableJWKSet<>(jwkSet);
	}

	@Bean
	public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
		return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
	}

	@Bean
	public AuthorizationServerSettings authorizationServerSettings() {
		return AuthorizationServerSettings.builder().build();
	}

	@Bean
	public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
		RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
				.clientId("demo-client")
				.clientIdIssuedAt(Instant.now())
				.clientSecret("{noop}demo-secret")
				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
				.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
				.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
				.authorizationGrantType(AuthorizationGrantType.PASSWORD)
				.redirectUri("https://baidu.com")
				.redirectUri("http://127.0.0.1:7100/login/oauth2/code/messaging-client-oidc")
				.redirectUri("http://127.0.0.1:7100/login/oauth2/code/demo-client-name")
				.tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofHours(1L)).build())
				.scope(OidcScopes.OPENID)
				.scope(OidcScopes.PROFILE)
				.scope(OidcScopes.EMAIL)
				.scope(OidcScopes.ADDRESS)
				.scope(OidcScopes.PHONE)
				.scope("client.create")
				.scope("client.read")
				// 不需要跳转到授权页面
//				.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
				.build();
		return new InMemoryRegisteredClientRepository(registeredClient);
	}

}

这里客户端、用户都是保存在内存里面的,为了方便演示。

# client_id: demo-client
# client_secret: demo-secret
# base64: ZGVtby1jbGllbnQ6ZGVtby1zZWNyZXQ=
# 在线base64: https://c.runoob.com/front-end/693/

### 查看oauth配置
GET {{baseUrl}}/.well-known/oauth-authorization-server

### 查看OpenID的配置
GET {{baseUrl}}/.well-known/openid-configuration

### jwks
GET {{baseUrl}}/oauth2/jwks

### 浏览器模式 - 浏览器
http://127.0.0.1:7000/oauth2/authorize?response_type=code&client_id=demo-client&redirect_uri=https://baidu.com&scope=openid client.read client.create

### 浏览器模式(方式一) - 授权码换取token
POST {{baseUrl}}/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic ZGVtby1jbGllbnQ6ZGVtby1zZWNyZXQ=

grant_type=authorization_code&redirect_uri=https://baidu.com&code=OHd9nQj4ykR3KqGSFk14dbCRx7ifO4Vu2R_SO5CEKvqFjd3FkKJRkfpW5IuLmj0gg8PXFc3oShRtTYqAs_Tzk9SaBnwGUFqvnqmFSljuawZKwjlVcmXflGRF0PJs3Q7B

### 浏览器模式(方式二) - 授权码换取token
POST {{baseUrl}}/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&redirect_uri=https://baidu.com&client_id=demo-client&client_secret=demo-secret&code=XmdmrulYday-0sxw0A1_5VBrWekozvFMzLECyG6rBV7G348Py453YOguQ5VKOilD4q2ihlEL_7_2fuKuatl5HaKf9YyTjr_3yHiiRlyGmsFO3Lf-rKixY2FoZ5rQnPSS

### 获取用户
GET {{baseUrl}}/userinfo
Authorization: Bearer {{accessToken}}

### 客户端模式
POST {{baseUrl}}/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic ZGVtby1jbGllbnQ6ZGVtby1zZWNyZXQ=

grant_type=client_credentials&scope=openid

### 注销令牌
POST {{baseUrl}}/oauth2/revoke
Content-Type: application/x-www-form-urlencoded
Authorization: Basic ZGVtby1jbGllbnQ6ZGVtby1zZWNyZXQ=

token_type_hint=refresh_token&token=klA-i51s2hVbankpkg9kRh9jk1EIFJnwHhdAYHbS7LoW9YBwMRvqSShOL_8h_LgunSytWh-08JveyLedHfAUD8ovrTjled6i7HYIgwKQUSP18zUTCThs-HV8AeboTtfX

idea里面可以直接发送请求
一、简单的Spring Authorization Server示例代码_第3张图片

你可能感兴趣的:(Spring,Authorization,Server,spring,数据库,sql)