Spring Security OAuth2.0 Authentication and authorization series > [Spring Security OAuth2.0 Authentication and authorization 1 : Framework building and certification testing ](https://blog.kdyzm.cn/post/24) > [Spring Security OAuth2.0 Authentication and authorization 2 : Build resource services ](https://blog.kdyzm.cn/post/25) > [Spring Security OAuth2.0 Authentication authorization 3 : Use JWT token ](https://blog.kdyzm.cn/post/26) The previous articles explained how to build authentication services and resource services from scratch , From issuing ordinary token to issuing jwt token , Finally it's done jwt Token issuance and verification . This article will explain how to authenticate and authorize in a distributed environment .## One 、 Design ideas  In general , A typical distributed system architecture is shown in the figure above , Here's a simple design , To complete the authentication and authorization under the distributed system . The overall design idea is to use OAuth2.0 Issue token , Use JWT Sign the token and issue JWT Token to client . Now that we have decided to use JWT The token , You don't need to call the authentication server to verify the token , Because JWT It contains all the information you need , And as long as the verification is successful , The token can be regarded as trusted and valid . As mentioned above , It can be designed like this :1. After the user requests to log in, the authentication service issues the token to the user , The browser stores the token .2. The browser carries a token when it requests resources , The gateway intercepts the request to verify the token , The method of verification is simple , Instead of requesting authentication service, use the key directly ( Symmetrical or asymmetrical ) Verification of signature , As long as the verification is successful, then jwt payload The information in is parsed into plaintext and put in the request header to forward the request to the resource service .3. Resource services get clear text information , Verify that you have permission to access the resource according to the permission information in the plaintext , If permission is granted, resource information will be returned , Without permission, return 401. To sum up , The whole idea is gateway certification , Resource Service Authentication . There will be a registry in a typical microservice architecture 、 Gateways and other services , Next, we will introduce and build related services in turn .## Two 、 The registry is built to facilitate local debugging , Here we use eureka server As a service registry , It's very easy to use ### 1. newly added maven Rely on ``` xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.boot
spring-boot-starter-actuator
```### 2. Create a new startup class ``` java@SpringBootApplication@EnableEurekaServerpublic class RegisterServer { public static void main(String[] args) { SpringApplication.run(RegisterServer.class,args); }}```### 3. New profile ``` yamlspring: application: name: register-serverserver: port: 8765 # Start port eureka: server: enable-self-preservation: false # Turn off server self protection , Client heartbeat detection 15 Within minutes the error reached 80% Service will protect , Leading others to think it's a good service eviction-interval-timer-in-ms: 10000 # Clean up intervals ( Unit millisecond , The default is 60*1000)5 Second, delete the service rejected by the client in the service registration list # shouldUseReadOnlyResponseCache: true #eureka yes CAP The theory is based on AP Strategy , To ensure strong consistency, turn off this switch CP Default does not close false Shut down client: register-with-eureka: false #false: Do not register as a client in the registry fetch-registry: false # For true When , It can start , But it's abnormal :Cannot execute request on any known server instance-info-replication-interval-seconds: 10 serviceUrl: defaultZone: http://localhost:${server.port}/eureka/ instance: hostname: ${spring.cloud.client.ip-address} prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}}``` Then start the startup class , Visit the browser ,[http://127.0.0.1:8765](http://127.0.0.1:8765), The following page appears to indicate success ## Two 、 The gateway is built here spring cloud gateway As a gateway ( No zuul)### 1. newly added maven Rely on ``` xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-gateway
2.2.5.RELEASE
org.springframework.boot
spring-boot-starter-actuator
org.projectlombok
lombok
org.springframework.security
spring-security-jwt
```### 2. Create a new startup class ``` java@SpringBootApplicationpublic class GatewayServer { public static void main(String[] args) { SpringApplication.run(GatewayServer.class, args); }}```### 3. New profile ``` yamlserver: port: 8761spring: cloud: gateway: routes: - id: resource_server uri: "lb://resource-server" predicates: - Path=/r** application: name: gateway-servereureka: client: service-url: defaultZone: http://127.0.0.1:8765/eureka instance: prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.ip‐address}:${spring.application.instance_id:${server.port}}``` such , A gateway is already built , But it doesn't have the authentication function we want .### 4. newly added token The knowledge points of global filter are as follows :- Global filter to achieve GlobalFilter Interface - In order to achieve token The filter is called first , To achieve Order Interface and maximize the priority - Use JwtHelper Tool class to jwt Verification of signature , Signed key Must be configured with the certification authority key bring into correspondence with - After the successful verification, the jwt in payload Put plaintext information in token-info Of header Value to the target service. The implementation code is as follows :```java@Component@Slf4jpublic class TokenFilter implements GlobalFilter, Ordered { private static final String BEAR_HEADER = "Bearer "; /** * The value should be equal to auth-server The signatures configured in are the same * * com.kdyzm.spring.security.auth.center.config.TokenConfig#SIGNING_KEY */ private static final String SIGNING_KEY = "auth123"; @Override public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION); // If not token, Then go straight back to 401 if(StringUtils.isEmpty(token)){ return unAuthorized(exchange); } // Verify and obtain PayLoad String payLoad; try { Jwt jwt = JwtHelper.decodeAndVerify(token.replace(BEAR_HEADER,""), new MacSigner(SIGNING_KEY)); payLoad = jwt.getClaims(); } catch (Exception e) { log.error(" Signature verification failed ",e); return unAuthorized(exchange); } // Will PayLoad Put the information in header ServerHttpRequest.Builder builder = exchange.getRequest().mutate(); builder.header("token-info", payLoad).build(); // Continue execution return chain.filter(exchange.mutate().request(builder.build()).build()); } private Mono
unAuthorized(ServerWebExchange exchange){ exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } /** * Set the priority of the filter to the highest , Because as long as the certification doesn't pass , You can't do anything * * @return */ @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; }}```## 3、 ... and 、 The original resource service has been integrated OAuth2.0、Spring Security、JWT Etc , According to the current design , Need to delete OAuth2.0 and JWT Components , Leave only Spring Security Components .### 1. remove OAuth2.0、JWT Components are deleted here maven Rely on , At the same time, delete the relevant configuration ** First step , Delete maven Rely on **, Just remove the following two dependencies ``` xml
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.security
spring-security-jwt
```** The second step , Delete related configuration ** Will ResouceServerConfig、TokenConfig Two classes are deleted directly that will do .### 2. Add a new filter here, you need to use a filter to do , First write a filter , Realize OncePerRequestFilter Interface , The function of the filter is to get the information from the gateway token-info Plain text , Package it as JwtTokenInfo thing , And add this information to SpringSecurity Context for later authentication . The code is implemented as follows :``` java@Component@Slf4jpublic class AuthFilterCustom extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { ObjectMapper objectMapper = new ObjectMapper(); String tokenInfo=request.getHeader("token-info"); if(StringUtils.isEmpty(tokenInfo)){ log.info(" Not found token Information "); filterChain.doFilter(request,response); return; } JwtTokenInfo jwtTokenInfo = objectMapper.readValue(tokenInfo, JwtTokenInfo.class); log.info("tokenInfo={}",objectMapper.writeValueAsString(jwtTokenInfo)); List
authorities1 = jwtTokenInfo.getAuthorities(); String[] authorities=new String[authorities1.size()]; authorities1.toArray(authorities); // Fill in user information and permissions To user identity token In objects UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(jwtTokenInfo.getUser_name(),null, AuthorityUtils.createAuthorityList(authorities)); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // Will authenticationToken Fill in the security context SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request,response); }}```### 3. Register the filter to the filter chain to modify WebSecurityConfig Class , Register the filter as follows :``` java.addFilterAfter(authFilterCustom, BasicAuthenticationFilter.class)// New filters ``` At the same time , Be sure to close session function , Otherwise, there will be context caching problems ``` java.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS);// Ban session``` The complete code is as follows :``` java @Autowired private AuthFilterCustom authFilterCustom; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf() .disable() .authorizeRequests()// .antMatchers("/r/r1").hasAuthority("p2")// .antMatchers("/r/r2").hasAuthority("p2") .antMatchers("/**").authenticated()// All requests must be authenticated .anyRequest().permitAll()// All other requests are freely accessible .and() .addFilterAfter(authFilterCustom, BasicAuthenticationFilter.class)// New filters .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS);// Ban session }```## Four 、 Other considerations certification services auth-server And resource services resource-server、 Gateway service gateway-server It's all about integration eureka client Components ## 5、 ... and 、 Before testing, you need to start each service in turn :- Start the registry register-server:[https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/register-server](https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/register-server)- Start the gate gateway-server:[https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/gateway-server](https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/gateway-server)- Start the authentication service auth-server:[https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/auth-server](https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/auth-server)- Start resource services resource-server:[https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/resource-server](https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/resource-server)** First step , Get token** Here we use password Patterns get directly token,POST Request the following interface :[http://127.0.0.1:30000/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=zhangsan&password=123](http://127.0.0.1:30000/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=zhangsan&password=123) You can get token.** The second step , Access resources ** Request resource service through gateway r1 Interface ,GET Request the following interface :[http://127.0.0.1:8761/r1](http://127.0.0.1:8761/r1) You need to bring Header,key For `Authorization`,value The format is as follows :``` textBearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiLCJST0xFX0FQSSJdLCJleHAiOjE2MTAzNzI5MzUsImF1dGhvcml0aWVzIjpbInAxIiwicDIiXSwianRpIjoiOWQzMzRmZGMtOTcwZC00YmJkLWI2MmMtZDU4MDZkNTgzM2YwIiwiY2xpZW50X2lkIjoiYzEifQ.gZraRNeX-o_jKiH7XQgg3TlUQBpxUcXa2-qR_Treu8U``` If the corresponding result is as follows , The test passed ``` Access resources r1``` Otherwise , Will return to 401 Status code .## 6、 ... and 、 Project source code project source code :[https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0](https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0) My original blog address :[https://blog.kdyzm.cn/post/30](https://blog.kdyzm.cn/p