The background,
In the previous section we learned the use of Spring Authorization Server. Here we simply record the use of Spring Resource Server.
Second, the demand for
The resource server provides two resources,userInfo and hello. UserInfo: The resource is a protected resource and requires user.userInfo permission to access it. Hello: The resource is public and can be accessed without permission.
Three, the analysis
1. How to verify that the token accessed in the resource server is valid?
Only JWT tokens are considered here.
The token is issued by the authorization server and is signed. Therefore, the resource server’s verification of the token requires the JWK information of the authorization server, which can be realized here by configuring JWTDecoder and filling in the JWK SET URI.
2. Where in the request did the token come from? The token can be obtained from the request header or request query param, so the BearerTokenResolver needs to be configured to implement it.
3, The permission field in the token will be prefixed with SCOPE_ by default, so you want to remove how to operate. Configuration JwtAuthenticationConverter object.
4. What if you add value to a JWT claim? Operate through JWTDecoder # SetClaimSetConverter. Deleting the contents of the claim can also be implemented here.
5. How to verify whether JWT is valid? This is done with the JWTDecoder # setJWTValidator method.
6. How to set the timeout for obtaining JWK from authorized server? Through JwkSetUriJwtDecoderBuilder# restOperations to operate.
Four, resource server authentication process
1. The request will beBearerTokenAuthenticationFilter
The interceptor intercepts and parses out of ittoken
If not resolved, it is handled by the next filter. Parse out and build oneBearerTokenAuthenticationToken
Object.
2. The next step will beHttpServletRequest
Passed to theAuthenticationManagerResolver
Object that is selected by itAuthenticationManager
Object, and then putBearerTokenAuthenticationToken
Passed to theAuthenticationManager
Object to authenticate.AuthenticationManager
The implementation of the object depends on what our token object isJWT
or opaque token
3. Validation failed
- empty
SecurityContextHolder
Object. - Under the
AuthenticationFailureHandler
Object handling.
4. Verification was successful
- will
Authentication
Object set toSecurityContextHolder
In the. - Leave to the remaining filters to continue processing.
Five, the realization of resource server
1. Introduce the JAR package
<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-security</artifactId>
</dependency>
2. Resource server configuration
package com.huan.study.resource.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; 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.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; /** * Resource server configuration ** @Author Huan. Fu 2021/7/16-5:00 PM */ @EnableWebSecurity public class ResourceserverConf extends WebSecurityConfigurerAdapter { private static final Logger log = LoggerFactory.getLogger(ResourceServerConfig.class); @Autowired private RestTemplateBuilder restTemplateBuilder; @Override protected void configure(HttpSecurity HTTP) throws Exception {http.authorizeRequests() // for userInfo S. Antmatchers ("/userInfo").access("hasAuthority('user.userInfo')"). And ().oAuth2ResourceServer ().jwt() // Decode JWT information .decoder(JWTDecoder (RestTemplateBuilder)) // Converts the JWT information to the JWTAuthenticationToken object JwtAuthenticationConverter (jwtAuthenticationConverter ()). And () / / access token from request to request the place BearerTokenResolver (bearerTokenResolver ()) / / at this point is authentication failed. The authenticationEntryPoint ((request, response, Exception) -> {// OAuth2 authentication failure, there is another possibility that is not OAuth2 authentication failure, such as no token passed, But access if protected by access method (exception instanceof OAuth2AuthenticationException) {OAuth2AuthenticationException oAuth2AuthenticationException = (OAuth2AuthenticationException) exception; OAuth2Error error = oAuth2AuthenticationException.getError(); The info (" authentication failed, exception types: [{}], exceptions: [{}] ", the exception. GetClass (). The getName (), error); } response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON.toString()); Response. GetWriter (), write (" {\ "code \" : 3, \ "message \" : \ "you have no permission to access \"} "); }) // Upon successful certification, AccessDeniedHandler ((request, response, exception) -> {log.info(" You have no permission to access, exception type :[{}]", exception.getClass().getName()); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON.toString()); Response. GetWriter (), write (" {\ \ "code \" : - 4 that "message \" : \ "you have no permission to access \"} "); }); } / * * * from request to request the local access to the token * / private BearerTokenResolver BearerTokenResolver () {DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver(); / / set the parameters of the request header, namely from the request header to token bearerTokenResolver. SetBearerTokenHeaderName (HttpHeaders. AUTHORIZATION); bearerTokenResolver.setAllowFormEncodedBodyParameter(false); / / if you can access token from the uri request parameter bearerTokenResolver. SetAllowUriQueryParameter (false); return bearerTokenResolver; } /** * Remove the prefix SCOPE_ from JWT scope * Set which field to get permission from JWT claim * If you need to get permission from more than one field or via a URL request, You will need to provide their own jwtAuthenticationConverter () implementation of this method, * * @ return jwtAuthenticationConverter * / private jwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); / / remove SCOPE_ prefix authoritiesConverter. SetAuthorityPrefix (" "); / / from JWT claim the fields in the access permissions, model obtained from the scope or SCP field authoritiesConverter. SetAuthoritiesClaimName (" scope "); converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); return converter; } /** * @return JWTDecoder */ public JWTDecoder JWTDecoder (RestTemplateBuilder Builder) {// Authoralize the server JWK information NimbusJwtDecoder decoder = NimbusJwtDecoder. WithJwkSetUri (" http://qq.com:8080/oauth2/jwks ") / / set for JWK information timeout .restOperations( builder.setReadTimeout(Duration.ofSeconds(3)) .setConnectTimeout(Duration.ofSeconds(3)) .build() ) .build(); / / check to JWT decoder. SetJwtValidator (JwtValidators. CreateDefault ()); / / the added value of the claim of JWT decoder. SetClaimSetConverter ( MappedJwtClaimSetConverter. WithDefaults (Collections. SingletonMap (" to add the key to the claim of ", the custom - > "value"))); return decoder; }}
There is a lot of customization done to the resource server here, so the configuration is long.
3, resources,
1 protected resource and 1 unprotected resource.
@RestController public class UserController {/** * This is a protected resource that requires user.userinfo permission to access. */ @GetMapping("userInfo") public Map<String, Object> userInfo(@AuthenticationPrincipal Jwt principal) { return new HashMap<String, Object>(4) {{ put("principal", principal); Put ("userInfo", "get userInfo"); }}; } / / @getMapping ("hello") public String hello() {return "hello does not need a protected resource "; }}
Six, test,
1. Access unprotected resources
You can see that there is no need for the token to be accessible.
2. Access protected resources
1. Token access is denied.
2. Then generate a token from the authorization server, which is the same authorization server used in the previous article.
3. After using token access, you can cash back and access resources.
4. Demonstrate the ability to add value to a Token claim.
5, presentations,userInfo
Is the need touser.userInfo
Permissions.
VII. Complete code
1. Authorization server, The authorization server https://gitee.com/huan1993/spring-cloud-parent/tree/master/security/authorization-server in article 2, the resource server https://gitee.com/huan1993/spring-cloud-parent/tree/master/security/resource-server
8. Refer to documentation
1, https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver