spring authorization server 0.3.1 - 默认oidc
- 开始
- 1、default-authorizationserver项目
- 1.1、AuthorizationServerConfig.java
- 1.2、DefaultSecurityConfig.java
- 1.3、Jwks.java
- 1.4、KeyGeneratorUtils.java
- 1.5、DefaultAuthorizationServer.java
- 1.6、application.yml
- 2、client项目
- 2.1、SecurityConfig.java
- 2.2、WebClientConfig.java
- 2.3、AuthorizationController.java
- 2.4、DefaultController.java
- 2.5、Client1Application.java
- 2.6、templates/index.html
- 2.7、application.yml
- 3、resource项目
- 3.1、ResourceServerConfig.java
- 3.2、MessagesController.java
- 3.3、MessageResourceApplication.java
- 3.4、application.yml
开始
spring security oauth已停更 spring security oauth migration guide ,新授权项目已迁移至spring authorization server,spring authorization server发展不容易,终于到了稍微稳当的版本。本文主要以源代码当中的示例为主,因源代码版本之间差异较大,部分示例代码会稍微改动。
演示代码请移步
spring authorization server default 示例代码
1、default-authorizationserver项目
1.1、AuthorizationServerConfig.java
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
.exceptionHandling(exceptions ->
exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// @formatter:on
return http.build();
}
// @formatter:off
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
// Save registered client in db as if in-memory
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
registeredClientRepository.save(registeredClient);
return registeredClientRepository;
}
// @formatter:on
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public ProviderSettings authorizationServerSettings() {
return ProviderSettings.builder().issuer("http://localhost:9000").build();
}
// @Bean
// public EmbeddedDatabase embeddedDatabase() {
// // @formatter:off
// return new EmbeddedDatabaseBuilder()
// .generateUniqueName(true)
// .setType(EmbeddedDatabaseType.H2)
// .setScriptEncoding("UTF-8")
// .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
// .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
// .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
// .build();
// // @formatter:on
// }
}
1.2、DefaultSecurityConfig.java
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class DefaultSecurityConfig {
// @formatter:off
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize ->
authorize.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
// @formatter:on
// @formatter:off
@Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user1")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
// @formatter:on
}
1.3、Jwks.java
public final class Jwks {
private Jwks() {
}
public static RSAKey generateRsa() {
KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
}
public static ECKey generateEc() {
KeyPair keyPair = KeyGeneratorUtils.generateEcKey();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
// @formatter:off
return new ECKey.Builder(curve, publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
}
public static OctetSequenceKey generateSecret() {
SecretKey secretKey = KeyGeneratorUtils.generateSecretKey();
// @formatter:off
return new OctetSequenceKey.Builder(secretKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
}
}
1.4、KeyGeneratorUtils.java
final class KeyGeneratorUtils {
private KeyGeneratorUtils() {
}
static SecretKey generateSecretKey() {
SecretKey hmacKey;
try {
hmacKey = KeyGenerator.getInstance("HmacSha256").generateKey();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return hmacKey;
}
static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
static KeyPair generateEcKey() {
EllipticCurve ellipticCurve = new EllipticCurve(
new ECFieldFp(
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")),
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
ECPoint ecPoint = new ECPoint(
new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
ECParameterSpec ecParameterSpec = new ECParameterSpec(
ellipticCurve,
ecPoint,
new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"),
1);
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(ecParameterSpec);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
}
1.5、DefaultAuthorizationServer.java
@SpringBootApplication
public class DefaultAuthorizationServer {
public static void main(String[] args) {
SpringApplication.run(DefaultAuthorizationServer.class, args);
}
}
1.6、application.yml
server:
port: 9000
spring:
devtools:
restart:
enabled: true
livereload:
enabled: true
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: 数据库用户名
password: 数据库密码
url: jdbc:mysql://localhost:3306/数据库名称?serverTimezone=GMT%2B8&&useSSL=false
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
org.springframework.security.oauth2: INFO
# org.springframework.boot.autoconfigure: DEBUG
2、client项目
2.1、SecurityConfig.java
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class SecurityConfig {
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/webjars/**");
}
// @formatter:off
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize ->
authorize.anyRequest().authenticated()
)
.oauth2Login(oauth2Login ->
oauth2Login.loginPage("/oauth2/authorization/messaging-client-oidc")
.defaultSuccessUrl("/", true)
)
.oauth2Client(withDefaults());
return http.build();
}
// @formatter:on
}
2.2、WebClientConfig.java
@Configuration
public class WebClientConfig {
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
@Bean
OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
2.3、AuthorizationController.java
@Controller
public class AuthorizationController {
private final WebClient webClient;
private final String messagesBaseUri;
public AuthorizationController(WebClient webClient,
@Value("${messages.base-uri}") String messagesBaseUri) {
this.webClient = webClient;
this.messagesBaseUri = messagesBaseUri;
}
@GetMapping(value = "/authorize", params = "grant_type=authorization_code")
public String authorizationCodeGrant(Model model,
@RegisteredOAuth2AuthorizedClient("messaging-client-authorization-code")
OAuth2AuthorizedClient authorizedClient) {
String[] messages = this.webClient
.get()
.uri(this.messagesBaseUri)
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String[].class)
.block();
model.addAttribute("messages", messages);
return "index";
}
// '/authorized' is the registered 'redirect_uri' for authorization_code
@GetMapping(value = "/authorized", params = OAuth2ParameterNames.ERROR)
public String authorizationFailed(Model model, HttpServletRequest request) {
String errorCode = request.getParameter(OAuth2ParameterNames.ERROR);
if (StringUtils.hasText(errorCode)) {
model.addAttribute("error",
new OAuth2Error(
errorCode,
request.getParameter(OAuth2ParameterNames.ERROR_DESCRIPTION),
request.getParameter(OAuth2ParameterNames.ERROR_URI))
);
}
return "index";
}
@GetMapping(value = "/authorize", params = "grant_type=client_credentials")
public String clientCredentialsGrant(Model model) {
String[] messages = this.webClient
.get()
.uri(this.messagesBaseUri)
.attributes(clientRegistrationId("messaging-client-client-credentials"))
.retrieve()
.bodyToMono(String[].class)
.block();
model.addAttribute("messages", messages);
return "index";
}
}
2.4、DefaultController.java
@Controller
public class DefaultController {
@GetMapping("/")
public String root() {
return "redirect:/index";
}
@GetMapping("/index")
public String index() {
return "index";
}
@ResponseBody
@GetMapping("/code")
public String code(String code) {
return code;
}
}
2.5、Client1Application.java
@SpringBootApplication
public class Client1Application {
public static void main(String[] args) {
SpringApplication.run(Client1Application.class, args);
}
}
2.6、templates/index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>Spring Security OAuth 2.0 Sample</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
</head>
<body>
<div th:fragment="header">
<nav class="navbar navbar-default">
<div class="container">
<div class="container-fluid">
<div class="navbar-collapse collapse" id="navbar">
</div>
</div>
</div>
</nav>
</div>
<div class="container">
<div th:if="${error}" class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 th:text="${error}" class="text-center"></h4>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Authorize the client using <span style="font-family:monospace">grant_type</span>:</h3>
</div>
<ul class="list-group">
<li class="list-group-item">
<a href="/authorize?grant_type=authorization_code" th:href="@{/authorize?grant_type=authorization_code}"><span style="font-size:medium">Authorization Code</span> <small class="text-muted">(Login to Spring Authorization Server using: user1/password)</small></a>
</li>
<li class="list-group-item">
<a href="/authorize?grant_type=client_credentials" th:href="@{/authorize?grant_type=client_credentials}"><span style="font-size:medium">Client Credentials</span></a>
</li>
</ul>
<div th:if="${messages}" class="panel-footer">
<h4>Messages:</h4>
<table class="table table-condensed">
<tbody>
<tr class="row" th:each="message : ${messages}">
<td th:text="${message}">message</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<script src="/webjars/jquery/jquery.min.js" th:src="@{/webjars/jquery/jquery.min.js}"></script>
<script src="/webjars/bootstrap/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
</body>
</html>
2.7、application.yml
server:
port: 8080
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
org.springframework.security.oauth2: INFO
# org.springframework.boot.autoconfigure: DEBUG
spring:
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
messaging-client-oidc:
provider: spring
client-id: messaging-client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "http://127.0.0.1:8080/login/oauth2/code/{registrationId}"
scope: openid, profile
client-name: messaging-client-oidc
messaging-client-authorization-code:
provider: spring
client-id: messaging-client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "http://127.0.0.1:8080/authorized"
scope: message.read,message.write
client-name: messaging-client-authorization-code
messaging-client-client-credentials:
provider: spring
client-id: messaging-client
client-secret: secret
authorization-grant-type: client_credentials
scope: message.read,message.write
client-name: messaging-client-client-credentials
provider:
spring:
issuer-uri: http://localhost:9000
messages:
base-uri: http://127.0.0.1:8090/messages
3、resource项目
3.1、ResourceServerConfig.java
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class ResourceServerConfig {
// @formatter:off
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable().cors().disable()
.securityMatcher("/messages/**", "/user/**")
.authorizeHttpRequests().anyRequest().authenticated()
.and()
// .authorizeHttpRequests()
// .requestMatchers("/messages/**").hasAuthority("SCOPE_message.read")
// .and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
// @formatter:on
}
3.2、MessagesController.java
@RestController
public class MessagesController {
@GetMapping("/messages")
public String[] getMessages() {
return new String[] {"Message 1", "Message 2", "Message 3"};
}
@GetMapping("/user")
public Jwt user() {
Jwt principal = (Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println(principal);
// 查询数据库获取用户信息
return principal;
}
}
3.3、MessageResourceApplication.java
@SpringBootApplication
public class MessagesResourceApplication {
public static void main(String[] args) {
SpringApplication.run(MessagesResourceApplication.class, args);
}
}
3.4、application.yml
server:
port: 8090
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
org.springframework.security.oauth2: INFO
# org.springframework.boot.autoconfigure: DEBUG
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:9000