paulwong

          OAUTH2 - SPRING SECURITY + KEYCLOAK

          根據(jù)OAUTH2協(xié)議,如果需要用戶協(xié)助的,則使用authorization_code流程,此時需要用戶登錄頁面、CLIENT SERVER、RESOURCE SERVER和AUTHORIZATION SERVER,其中CLIENT SERVER是通過http調(diào)用RESOURCE SERVER的api,AUTHORIZATION SERVER使用現(xiàn)成的KEYCLOAK。

          如果不需要用戶協(xié)助的,即SERVER對SERVER的,則適用client_credentials流程,此時需要CLIENT SERVER、RESOURCE SERVER和AUTHORIZATION SERVER。

          通常HTTP請求會在HEADER中夾帶ACCESS_TOKEN,格式為JWT。

          RESOURCE SERVER是如何知道該次的HTTP請求是合法的呢,只需用AUTHORIZATION SERVER提供的PUBLIC KEY能正常解密ACCESS_TOKEN,且不過期,則是合法的請求。

          如果需要做授權(quán)認(rèn)證,則檢查JWT中的ROLE字符是否是事先定義的字符,如是則認(rèn)為有權(quán)訪問,否則拒絕。

          RESOURCE SERVER

          pom.xml
          <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
              <parent>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-parent</artifactId>
                  <version>2.5.6</version>
                  <relativePath /> <!-- lookup parent from repository -->
              </parent>
              <groupId>com.paul</groupId>
              <artifactId>test-oauth2-department-service</artifactId>
              <version>0.0.1-SNAPSHOT</version>
              <name>test-oauth2</name>
              <description>Demo project for Spring Boot</description>
              <properties>
                  <java.version>1.8</java.version>
              </properties>
              
              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-webflux</artifactId>
                  </dependency>
                  
                  <dependency>
                      <groupId>org.mariadb.jdbc</groupId>
                      <artifactId>mariadb-java-client</artifactId>
                  </dependency>


                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-test</artifactId>
                      <scope>test</scope>
                  </dependency>
              </dependencies>

              <build>
                  <plugins>
                      <plugin>
                          <groupId>org.springframework.boot</groupId>
                          <artifactId>spring-boot-maven-plugin</artifactId>
                          <configuration>
                              <layout>ZIP</layout>
                              <excludes>
                                  <exclude>
                                      <groupId>*</groupId>
                                      <artifactId>*</artifactId>
                                  </exclude>
                              </excludes>
                              <includes>
                                  <include>
                                      <groupId>com.paul</groupId>
                                  </include>
                              </includes>
                          </configuration>
                      </plugin>
                  </plugins>
              </build>

          </project>

          application.yaml
          server:
             port: 8281
             
          spring:
            security:
              oauth2:
                #check OAuth2ResourceServerProperties
                resourceserver:
                  jwt:
                    #jwk-set-uri: "${rest.security.issuer-uri}/protocol/openid-connect/certs"
                    issuer-uri: "${rest.security.issuer-uri}"
                    #public-key-location: "classpath:/key.pub"


          #Logging Configuration

          logging:
             level:
                org.springframework.boot.autoconfigure.logging: INFO
                org.springframework.security: DEBUG
                com.paul: DEBUG
                root: INFO

          java配置文件
          package com.paul.testoauth2.config;

          import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.http.HttpMethod;
          import org.springframework.security.config.annotation.web.builders.HttpSecurity;
          import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
          import org.springframework.security.oauth2.jwt.JwtDecoder;
          import org.springframework.security.oauth2.jwt.JwtDecoders;
          import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
          import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
          import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;

          import com.fasterxml.jackson.databind.ObjectMapper;
          import com.paul.testoauth2.oauth2.converter.KeycloakRealmRoleConverter;
          import com.paul.testoauth2.oauth2.converter.UsernameSubClaimAdapter;

          @Configuration
          public class SecurityConfigurer extends WebSecurityConfigurerAdapter{
              
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  // @formatter:off
                  http
                      .authorizeRequests(
                          a -> a.antMatchers("/", "/error", "/webjars/**")
                                .permitAll()
                                .antMatchers(HttpMethod.GET, "/protected/**").hasRole("READ_DEPARTMENT")
                                .anyRequest()
                                .authenticated()
                       )
                      .exceptionHandling(
                          e -> e//.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
                                .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
          //                      .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
                      )
                      .oauth2ResourceServer(
                          o -> o.jwt(
                                    j -> j.jwtAuthenticationConverter(jwtAuthenticationConverter(null))
                                            .decoder(jwtDecoder(null))
                                 )
                       )
          //            .decoder(jwtDecoder(null))
                      ;
                  // @formatter:on
              }
              
              @Bean
              public JwtAuthenticationConverter jwtAuthenticationConverter(ObjectMapper objectMapper) {
                  JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
                  jwtConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter(objectMapper));
                  
                  return jwtConverter;
              }
              
              //below is auto configured at OAuth2ResourceServerJwtConfiguration
              @Bean
              public JwtDecoder jwtDecoder(OAuth2ResourceServerProperties properties) {
                  String issuerUri = properties.getJwt().getIssuerUri();
                  // Use preferred_username from claims as authentication name, instead of UUID subject
                  NimbusJwtDecoder jwtDecoder = 
                          JwtDecoders.<NimbusJwtDecoder>fromIssuerLocation(issuerUri);
                  jwtDecoder.setClaimSetConverter(new UsernameSubClaimAdapter());
                  return jwtDecoder;
              }

          }


          CLIENT SERVER

          pom.xml
          <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
              <parent>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-parent</artifactId>
                  <version>2.5.6</version>
                  <relativePath /> <!-- lookup parent from repository -->
              </parent>
              <groupId>com.paul</groupId>
              <artifactId>test-oauth2</artifactId>
              <version>0.0.1-SNAPSHOT</version>
              <name>test-oauth2</name>
              <description>Demo project for Spring Boot</description>
              <properties>
                  <java.version>1.8</java.version>
              </properties>
              
              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-oauth2-client</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-webflux</artifactId>
                  </dependency>
                  
                  <dependency>
                      <groupId>org.mariadb.jdbc</groupId>
                      <artifactId>mariadb-java-client</artifactId>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-data-jdbc</artifactId>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-test</artifactId>
                      <scope>test</scope>
                  </dependency>
              </dependencies>

              <build>
                  <plugins>
                      <plugin>
                          <groupId>org.springframework.boot</groupId>
                          <artifactId>spring-boot-maven-plugin</artifactId>
                          <configuration>
                              <layout>ZIP</layout>
                              <excludes>
                                  <exclude>
                                      <groupId>*</groupId>
                                      <artifactId>*</artifactId>
                                  </exclude>
                              </excludes>
                              <includes>
                                  <include>
                                      <groupId>com.paul</groupId>
                                  </include>
                              </includes>
                          </configuration>
                      </plugin>
                  </plugins>
              </build>

          </project>

          application.yaml
          server:
             port: 8280
             
          spring:
            security:
              oauth2:
                #check OAuth2ResourceServerProperties
                resourceserver:
                  jwt:
                    #jwk-set-uri: "${rest.security.issuer-uri}/protocol/openid-connect/certs"
                    issuer-uri: "${rest.security.issuer-uri}"
                    #public-key-location: "classpath:/key.pub"
                         
                #check OAuth2ClientProperties

                client:
                  registration:
                    my-client-1:
                      client-id: "test-employee-service"
                      client-secret: ${client-secret.my-client-1}
                      client-name: "test-employee-service"
                      provider: "keycloak-provider"
                      scope: "openid"
                      #redirect-uri: "https://my-redirect-uri.com"
                      client-authentication-method: "basic"
                      authorization-grant-type: "client_credentials"
                    app-springboot-confidential:
                      client-id: "app-springboot-confidential"
                      client-secret: ${client-secret.app-springboot-confidential}
                      client-name: "app-springboot-confidential"
                      provider: "keycloak-provider"
                      scope: "openid"
                      redirect-uri: "https://my-redirect-uri.com"
                      client-authentication-method: "basic"
                      authorization-grant-type: "authorization_code"

                  provider:
                    keycloak-provider:
                      issuer-uri : "${rest.security.issuer-uri}"
                      user-name-attribute: "preferred_username"


          #Logging Configuration
          logging:
             level:
                org.springframework.boot.autoconfigure.logging: INFO
                org.springframework.security: DEBUG
                com.paul: DEBUG
                root: INFO

          SecurityConfigurer.java
          package com.paul.testoauth2.config;

          import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.http.HttpMethod;
          import org.springframework.jdbc.core.JdbcOperations;
          import org.springframework.security.config.annotation.web.builders.HttpSecurity;
          import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
          import org.springframework.security.oauth2.client.JdbcOAuth2AuthorizedClientService;
          import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
          import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
          import org.springframework.security.oauth2.jwt.JwtDecoder;
          import org.springframework.security.oauth2.jwt.JwtDecoders;
          import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
          import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
          import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;

          import com.fasterxml.jackson.databind.ObjectMapper;
          import com.paul.testoauth2.oauth2.converter.KeycloakRealmRoleConverter;
          import com.paul.testoauth2.oauth2.converter.UsernameSubClaimAdapter;

          @Configuration
          //@EnableGlobalMethodSecurity(prePostEnabled = true)
          public class SecurityConfigurer extends WebSecurityConfigurerAdapter{
              
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  // @formatter:off
                  http
                      .authorizeRequests(
                          a -> a
                                  .antMatchers("/", "/error", "/webjars/**")
                                .permitAll()
                                .antMatchers(HttpMethod.GET, "/protected/**").hasRole("USER")
                                .antMatchers(HttpMethod.GET, "/api/employees/**").hasRole("READ_EMPLOYEE")
                                .anyRequest()
                                .authenticated()
                       )
                      .exceptionHandling(
                          e -> e//.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
                                .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
          //                      .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
                       )
                      .oauth2Client(
                          c -> c.authorizedClientService(
                                    authorizedClientService(nullnull)
                                 )
                       )
                      .oauth2ResourceServer()
                      .jwt(
                          c -> c.jwtAuthenticationConverter(
                                    jwtAuthenticationConverter(null)
                                 )
                                .decoder(jwtDecoder(null))
                       )
          //            .decoder(jwtDecoder(null))
                      ;
                  // @formatter:on
              }
              
              @Bean
              public OAuth2AuthorizedClientService authorizedClientService(
                  JdbcOperations jdbcOperations, ClientRegistrationRepository clientRegistrationRepository
              ) {
                  return new JdbcOAuth2AuthorizedClientService(jdbcOperations, clientRegistrationRepository);
              }
              
              @Bean
              public JwtAuthenticationConverter jwtAuthenticationConverter(ObjectMapper objectMapper) {
                  JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
                  jwtConverter.setPrincipalClaimName("preferred_username");
                  jwtConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter(objectMapper));
                  return jwtConverter;
              }
              
              //below is auto configured at OAuth2ResourceServerJwtConfiguration
              @Bean
              public JwtDecoder jwtDecoder(OAuth2ResourceServerProperties properties) {
                  String issuerUri = properties.getJwt().getIssuerUri();
                  // Use preferred_username from claims as authentication name, instead of UUID subject
                  NimbusJwtDecoder jwtDecoder = 
                          JwtDecoders.<NimbusJwtDecoder>fromIssuerLocation(issuerUri);
                  jwtDecoder.setClaimSetConverter(new UsernameSubClaimAdapter());
                  return jwtDecoder;
              }

          }


          WebClientConfig.java
          package com.paul.testoauth2.config;

          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
          import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
          import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
          import org.springframework.web.reactive.function.client.WebClient;

          @Configuration
          public class WebClientConfig {

              @Bean
              public WebClient webClient(ClientRegistrationRepository clientRegistrations,
                      OAuth2AuthorizedClientRepository authorizedClients) {
                  ServletOAuth2AuthorizedClientExchangeFilterFunction oauth = 
                          new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
                  oauth.setDefaultClientRegistrationId("my-client-1");
                  return WebClient.builder().filter(oauth).build();
              }

          }

          DepartmentRestClient.java
          package com.paul.testoauth2.employee.service;


          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.beans.factory.annotation.Value;
          import org.springframework.stereotype.Component;
          import org.springframework.web.reactive.function.client.WebClient;

          @Component
          public class DepartmentRestClient {
              
          //    @NotNull
              @Value("${department-service.url}")
              private String endpoint;

              @Autowired
              private WebClient webClient;

              public String getDepartmentName() {
                  return webClient.get()
                                  .uri(endpoint + "/protected")
                                  .retrieve()
                                  .bodyToMono(String.class)
                                  .block()
                                  ;
              }
          }


          Reference:
          https://docs.spring.io/spring-security/site/docs/5.5.x/reference/html5/#oauth2

          Sample Project:
          https://github.com/spring-projects/spring-security-samples/tree/5.5.x

          !相當(dāng)不錯的教程
          https://wstutorial.com/index.html
          https://wstutorial.com/rest/spring-security-oauth2-keycloak.html
          https://wstutorial.com/rest/spring-security-oauth2-keycloak-roles.html






          posted on 2021-11-03 16:58 paulwong 閱讀(752) 評論(0)  編輯  收藏 所屬分類: OAUTH2

          主站蜘蛛池模板: 仁布县| 理塘县| 巴中市| 胶州市| 远安县| 吉木萨尔县| 五常市| 佛冈县| 甘肃省| 吉林省| 商丘市| 临朐县| 安仁县| 卓尼县| 新龙县| 武胜县| 唐山市| 沈阳市| 和林格尔县| 建阳市| 汉源县| 五家渠市| 洞口县| 栖霞市| 阜阳市| 神农架林区| 巍山| 三亚市| 岚皋县| 涿鹿县| 紫金县| 屯门区| 天柱县| 德化县| 丹东市| 临西县| 尚义县| 宣武区| 寻乌县| 周宁县| 防城港市|