Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize distributed system Authorization by Spring Security

2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly introduces Spring Security how to achieve distributed system authorization, has a certain reference value, interested friends can refer to, I hope you can learn a lot after reading this article, the following let the editor take you to understand it.

1 demand analysis

The review of the technical solution is as follows:

1. UAA authentication service is responsible for authentication and authorization.

2. All requests go through the gateway to the micro-service

3. The gateway is responsible for authenticating clients and forwarding requests

4. The gateway parses the token and passes it to the microservice, which authorizes the microservice.

2 Registration Center

All requests for microservices go through the gateway, which reads the address of the microservice from the registry and forwards the request to the microservice.

This section completes the construction of the registry, which uses Eureka.

1. Create a maven project

2. Pom.xml depends on the following

Distributed-security com.lw.security 1.0-SNAPSHOT 4.0.0 distributed-security-discovery org.springframework.cloud spring-cloud-starter-netflix-eureka-server org.springframework.boot spring-boot-starter-actuator

3. Configuration file

Configure application.yml in resources

Spring: application: name: distributed-discoveryserver: port: 53000 # Startup port eureka: server: enable-self-preservation: false # disable server self-protection, client heartbeat detection error reaches 80% within 15 minutes, the service will protect, resulting in a service that others consider to be useful eviction-interval-timer-in-ms: 10000 # cleanup interval (in milliseconds) The default is 60: 1000) the service that removes the client in 5 seconds is removed from the service registration list # shouldUseReadOnlyResponseCache: true # eureka is a CAP theory based on AP policy. To ensure strong consistency, turn this switch off. CP does not turn off false by default. Close client: register-with-eureka: false # false: do not register as a client in the registry fetch-registry: false # is true, you can start Exception can be reported: 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}}

Startup class:

@ SpringBootApplication@EnableEurekaServerpublic class DiscoveryServer {public static void main (String [] args) {SpringApplication.run (DiscoveryServer.class, args);}} 3 Gateway

There are two ways to integrate OAuth3.0 in the gateway, one is that the authentication server generates jwt tokens, and all requests are verified and judged in the gateway layer, and the other is handled by various resource services, and the gateway only forwards requests.

We choose the first one. We use the API gateway as the resource server of OAuth3.0 to intercept access client permissions, resolve tokens and forward current login user information (jsonToken) to micro-services, so that downstream micro-services do not need to care about token format resolution and OAuth3.0-related mechanisms.

The API gateway is mainly responsible for two things in the authentication and authorization system:

(1) as the resource server role of OAuth3.0, the access party's permissions are intercepted.

(2) the token parses and forwards the current login user information (plaintext token) to the micro service

After getting the plaintext token (plaintext token contains the identity and permission information of the logged-in user), the microservice also needs to do two things:

(1) user authorization to intercept (depending on whether the current user has access to the resource)

(2) store user information in the current thread context (it is beneficial for subsequent business logic to obtain current user information at any time)

3.1 create a project

1 、 pom.xml

Distributed-security com.lw.security 1.0-SNAPSHOT 4.0.0 distributed-security-gateway org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-netflix-hystrix org.springframework.cloud Spring-cloud-starter-netflix-ribbon org.springframework.cloud spring-cloud-starter-openfeign com.netflix.hystrix hystrix-javanica org.springframework.retry spring-retry org.springframework.boot spring-boot-starter -actuator org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-zuul org.springframework.cloud spring-cloud-starter-security org.springframework.cloud spring-cloud -starter-oauth3 org.springframework.security spring-security-jwt javax.interceptor javax.interceptor-api com.alibaba fastjson org.projectlombok lombok

2. Configuration file

Configure application.properties

Spring.application.name=gateway-serverserver.port=53010spring.main.allow-bean-definition-overriding = truelogging.level.root = infologging.level.org.springframework = infozuul.retryable = truezuul.ignoredServices = * zuul.add-host-header = truezuul.sensitiveHeaders = * zuul.routes.uaa-service.stripPrefix = falsezuul.routes.uaa-service.path = / uaa/**zuul.routes.order-service.stripPrefix = falsezuul.routes.order-service.path = / order/**eureka.client.serviceUrl.defaultZone = http://localhost: 53000/eureka/eureka.instance.preferIpAddress = trueeureka.instance.instance-id = ${spring.application.name}: ${spring.cloud.client.ip-address}: ${spring.application.instance_id:$ {server.port}} management.endpoints.web.exposure.include = refresh Health,info,envfeign.hystrix.enabled = truefeign.compression.request.enabled = truefeign.compression.request.mime-types [0] = text/xmlfeign.compression.request.mime-types [1] = application/xmlfeign.compression.request.mime-types [2] = application/jsonfeign.compression.request.min-request-size = 2048feign.compression.response.enabled = true

Unified Authentication Service (UAA) and Unified user Service are both micro-services under the gateway, so you need to add a routing configuration on the gateway:

Zuul.routes.uaa-service.stripPrefix = falsezuul.routes.uaa-service.path = / uaa/**zuul.routes.user-service.stripPrefix = falsezuul.routes.user-service.path = / order/**

It is configured that if the request url received by the gateway conforms to the / order/** expression, it will be forwarded to order-service (Unified user Service).

Startup class:

@ SpringBootApplication @ EnableZuulProxy@EnableDiscoveryClientpublic class GatewayServer {public static void main (String [] args) {SpringApplication.run (GatewayServer.class, args);}} 3.2 token configuration

As mentioned earlier, because the resource server needs to verify and parse the token, it can often be done by exposing the Endpoint of check_token on the authorization server, while we use symmetrically encrypted jwt in the authorization server, so we only need to know the key. The resource service and authorization service are symmetrically designed, so we can copy the two classes of TokenConfig of the authorization service.

@ Configurationpublic class TokenConfig {private String SIGNING_KEY = "uaa123"; @ Bean public TokenStore tokenStore () {return new JwtTokenStore (accessTokenConverter ());} @ Bean public JwtAccessTokenConverter accessTokenConverter () {JwtAccessTokenConverter converter = new JwtAccessTokenConverter (); converter.setSigningKey (SIGNING_KEY); / / symmetric key, which is used by the resource server to decrypt return converter;} 3.3 configure the resource service

Define the resource service configuration in ResouceServerConfig. The main content of the configuration is to define some matching rules that describe what permissions an access client needs to access a micro-service, such as:

@ Configurationpublic class ResouceServerConfig {public static final String RESOURCE_ID = "res1"; / * * Unified Certification Service (UAA) resource interception * / @ Configuration @ EnableResourceServer public class UAAServerConfig extends ResourceServerConfigurerAdapter {@ Autowired private TokenStore tokenStore @ Override public void configure (ResourceServerSecurityConfigurer resources) {resources.tokenStore (tokenStore) .resourceId (RESOURCE_ID) .stateless (true);} @ Override public void configure (HttpSecurity http) throws Exception {http.authorizeRequests () .antMatrices ("/ uaa/**"). PermitAll () }} / * order Service * / @ Configuration @ EnableResourceServer public class OrderServerConfig extends ResourceServerConfigurerAdapter {@ Autowired private TokenStore tokenStore; @ Override public void configure (ResourceServerSecurityConfigurer resources) {resources.tokenStore (tokenStore) .resourceId (RESOURCE_ID) .stateless (true) } @ Override public void configure (HttpSecurity http) throws Exception {http .authorizeRequests () .antMatrices ("/ order/**") .access ("# oauth3.hasScope ('ROLE_API')");}

Two resources for microservices are defined above, among which:

UAAServerConfig specifies that the / uaa/** gateway will not intercept if the request matches.

OrderServerConfig specifies that if the request matches / order/**, that is, to access the unified user service, the access client needs to include read in scope and ROLE_USER in authorities (permission).

Because res1 is an access client, read includes three permissions of ROLE_ADMIN,ROLE_USER,ROLE_API.

3.4 Security configuration @ Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@ Override protected void configure (HttpSecurity http) throws Exception {http .authorizeRequests () .a ntMatchers ("/ * *") .permitAll () .and () .csrf () .disable ();} 4 forward plaintext token to the microservice

Through the Zuul filter, the purpose is to make it easy for downstream micro-services to obtain the current login user information (plaintext token)

(1) implement the Zuul pre-filter, extract the information of the currently logged-in user, and put it in the request that forwards the microservice

/ * token delivery interception * / public class AuthFilter extends ZuulFilter {@ Override public boolean shouldFilter () {return true;} @ Override public String filterType () {return "pre";} @ Override public int filterOrder () {return 0;} @ Override public Object run () {/ * 1. Get token content * / RequestContext ctx = RequestContext.getCurrentContext (); Authentication authentication = SecurityContextHolder.getContext (). GetAuthentication (); if (! (authentication instanceof OAuth3Authentication)) {/ / No token access to resources in the gateway. Currently, only the uua service directly exposes return null;} OAuth3Authentication oauth3Authentication = (OAuth3Authentication) authentication; Authentication userAuthentication = oauth3Authentication.getUserAuthentication () Object principal = userAuthentication.getPrincipal (); / * * 2. Assemble plaintext token, forward it to microservice, and put it into header with the name json-token * / List authorities = new ArrayList (); userAuthentication.getAuthorities (). Stream (). ForEach (s-> authorities.add (GrantedAuthority) s). GetAuthority ()); OAuth3Request oAuth3Request = oauth3Authentication.getOAuth3Request (); Map requestParameters = oAuth3Request.getRequestParameters (); Map jsonToken = new HashMap (requestParameters) If (userAuthentication! = null) {jsonToken.put ("principal", userAuthentication.getName ()); jsonToken.put ("authorities", authorities);} ctx.addZuulRequestHeader ("json-token", EncryptUtil.encodeUTF8StringBase64 (JSON.toJSONString (jsonToken); return null;}}

Build EncryptUtil class UTF8 interchange Base64 under common package

Public class EncryptUtil {private static final Logger logger = LoggerFactory.getLogger (EncryptUtil.class); public static String encodeBase64 (byte [] bytes) {String encoded = Base64.getEncoder (). EncodeToString (bytes); return encoded;} public static byte [] decodeBase64 (String str) {byte [] bytes = null; bytes = Base64.getDecoder (). Decode (str); return bytes; public static String encodeUTF8StringBase64 (String str) {String encoded = null Try {encoded = Base64.getEncoder () .encodeToString (str.getBytes ("utf-8"));} catch (UnsupportedEncodingException e) {logger.warn ("unsupported encoding format", e);} public static String decodeUTF8StringBase64 (String str) {String decoded = null; byte [] bytes = Base64.getDecoder () .decode (str) Decoded = new String (bytes, "utf-8");} catch (UnsupportedEncodingException e) {return decoded; public static String encodeURL (String url) {String encoded = null; try {encoded = URLEncoder.encode (url, "utf-8") } catch (UnsupportedEncodingException e) {logger.warn ("URLEncode failure", e);} return encoded;} public static String decodeURL (String url) {String decoded = null; decoded = URLDecoder.decode (url, "utf-8") Logger.warn ("URLDecode failure", e); return decoded; public static void main (String [] args) {String str = "abcd {'a'afia args'}"; String encoded = EncryptUtil.encodeUTF8StringBase64 (str); String decoded = EncryptUtil.decodeUTF8StringBase64 (encoded); System.out.println (str); System.out.println (encoded) System.out.println (decoded); String url = "= = wo"; String urlEncoded = EncryptUtil.encodeURL (url); String urlDecoded = EncryptUtil.decodeURL (urlEncoded); System.out.println (url); System.out.println (urlEncoded); System.out.println (urlDecoded);}

(2) include filter in the spring container:

Configure AuthFilter

@ Configurationpublic class ZuulConfig {@ Bean public AuthFilter preFileter () {return new AuthFilter ();} @ Bean public FilterRegistrationBean corsFilter () {final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (); final CorsConfiguration config = new CorsConfiguration (); config.setAllowCredentials (true); config.addAllowedOrigin ("*"); config.addAllowedHeader ("*"); config.addAllowedMethod ("*"); config.setMaxAge (18000L) Source.registerCorsConfiguration ("/ * *", config); CorsFilter corsFilter = new CorsFilter (source); FilterRegistrationBean bean = new FilterRegistrationBean (corsFilter); bean.setOrder (Ordered.HIGHEST_PRECEDENCE); return bean;}} 5 Micro service user authentication interception

How to authenticate and intercept when a microservice receives a plaintext token? Implement a filter by yourself? Parse the plaintext token and define a set of resource access policies. Can it adapt to Spring Security? is it suddenly reminiscent of the example of Spring Security authentication based on token that we implemented earlier? We also take the unified user service as the gateway downstream micro-service, transform it, and increase the micro-service user authentication interception function.

(1) increase test resources

OrderController adds the following endpoint

@ PreAuthorize ("hasAuthority ('p1')") @ GetMapping (value = "/ R1") public String R1 () {UserDTO user = (UserDTO) SecurityContextHolder.getContext (). GetAuthentication (). GetPrincipal (); return user.getUsername () + "access Resource 1" } @ PreAuthorize ("hasAuthority ('p2')") @ GetMapping (value = "/ R2") public String R2 () {/ / get the current login user UserDTO user = (UserDTO) SecurityContextHolder.getContext (). GetAuthentication (). GetPrincipal (); return user.getUsername () + "access Resource 2";}

Add entity class UserDto to model package

@ Datapublic class UserDTO {private String id; private String username; private String mobile; private String fullname;}

(2) Spring Security configuration

Enable method protection, and add Spring configuration policy. Except / login method is not protected (unified authentication needs to be called), all other resources need authentication to access.

@ Override public void configure (HttpSecurity http) throws Exception {http .authorizeRequests () .antMatrices ("/ * *") .access ("# oauth3.hasScope ('ROLE_ADMIN')") .and () .csrf () .disable () .sessionManageme nt () .sessionCreationPolicy (SessionCreationPolicy.STATELESS);}

Combining the above configuration, we have defined a total of three resources, with p1 permission to access R1 resources, p2 permissions to access R2 resources, and R3 resources as long as the authentication is passed.

(3) define filter to intercept token and form the Authentication object of Spring Security

Componentpublic class TokenAuthenticationFilter extends OncePerRequestFilter {@ Override protected void doFilterInternal (HttpServletRequest httpServletRequest, HttpServletResponsehttpServletResponse, FilterChain filterChain) throws ServletException, IOException {String token = httpServletRequest.getHeader ("json-token"); if (token! = null) {/ / 1. Parse token String json = EncryptUtil.decodeUTF8StringBase64 (token); JSONObject userJson = JSON.parseObject (json); UserDTO user = new UserDTO (); user.setUsername (userJson.getString ("principal")); JSONArray authoritiesArray = userJson.getJSONArray ("authorities"); String [] authorities = authoritiesArray.toArray (newString [authoritiesArray.size ()]); / / 2. Create and populate authentication UsernamePasswordAuthenticationToken authentication = newUsernamePasswordAuthenticationToken (user, null, AuthorityUtils.createAuthorityList (authorities)); authentication.setDetails (new WebAuthenticationDetailsSource (). BuildDetails (httpServletRequest)); / / 3. Save authentication into the security context SecurityContextHolder.getContext (). SetAuthentication (authentication);} filterChain.doFilter (httpServletRequest, httpServletResponse);}}

Through the worry device above, the user's identity information can be easily obtained in the resource service:

UserDTO user = (UserDTO) SecurityContextHolder.getContext (). GetAuthentication (). GetPrincipal ()

Or three steps:

1. Parsing token

two。 Create and populate authentication

3. Save authentication to the security context

Leave the rest to Spring Security.

6 Integration testing

Note: remember the pom import eurika coordinates of uaa and order, as well as application.properties configuration eurika

The test process for this case describes:

1. Use OAuth3.0 password mode to obtain token from UAA.

2. Use the token to access the test resources of the order service through the gateway

(1) access the authorization of the uaa through the gateway and obtain the token and obtain the token. Notice that port is 53010, the port of the gateway.

Such as authorized endpoint:

Http://localhost:53010/uaa/oauth/authorize?response_type=code&client_id=c1

Token endpoint

Http://localhost:53010/uaa/oauth/token

(2) use Token to access the r1-r2 test resources in the order service through the gateway for testing.

Results:

Use Zhang San token to access p1, and the access is successful

Access to p2 using Zhang San token failed

Use Li Si token to access p1, the access failed

Use Li Si token to access p2, and the access is successful

It is in line with the expected results.

(3) destroy the token test

No token test returns:

{"error": "unauthorized", "error_description": "Full authentication is required to access this resource"}

Break the token test return:

{"error": "invalid_token", "error_description": "Cannot convert access token to JSON"} 7 expand user information 7.1 requirement analysis

At present, the jwt token stores the user's identity information and permission information, and the gateway forwards the token culture to the micro-service for use. at present, the user's identity information only includes the user's account, and the micro-service also needs the user's ID, mobile phone number and other important information.

Therefore, this case will provide ideas and methods to expand user information to meet the needs of micro-services to use user information.

The following analyzes the scheme for extending user information in JWT tokens:

In the authentication phase, DaoAuthenticationProvider will call UserDetailService to query the user's information, here you can get a full range of user information. Since the identity information of the user in the JWT token comes from only the identity information of the user defined in UserDetails,UserDetails, there are two ideas: the first is that UserDetails can be extended to include more custom attributes, and the second is that the content of username can be extended, such as storing json data as the content of username. Comparatively speaking, the second scheme is relatively simple and does not need to destroy the structure of the UserDetails, we adopt the second scheme.

7.2 modify UserDetailService

Query from the database to user, and convert the whole user to json and store it in the userDetails object.

@ Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {/ / login account System.out.println ("username=" + username); / / go to the database to query according to the account. UserDto user = userDao.getUserByUsername (username); if (user = = null) {return null;} / / query user rights List permissions = userDao.findPermissionsByUserId (user.getId ()); String [] perarray = new String [permissions.size ()]; permissions.toArray (perarray); / / create userDetails / / here user is converted to json, and the whole user is stored in userDetails String principal = JSON.toJSONString (user) UserDetails userDetails = User.withUsername (principal) .password (user.getPassword ()) .authorities (perarray) .build (); return userDetails;} 7.3.Modification of the resource service overfilter

The worrier in the resource service is responsible for parsing the json-token from the header, from which you can get the user identity information put into the gateway. Some key codes are as follows:

... If (token! = null) {/ / 1. Parse token String json = EncryptUtil.decodeUTF8StringBase64 (token); JSONObject userJson = JSON.parseObject (json); / / fetch user identity information String principal = userJson.getString ("principal"); / / convert json to object UserDTO userDTO = JSON.parseObject (principal, UserDTO.class); JSONArray authoritiesArray = userJson.getJSONArray ("authorities"); Thank you for reading this article carefully. I hope the article "how to achieve distributed system Authorization in Spring Security" shared by the editor will be helpful to everyone. At the same time, I also hope that you will support us and pay attention to the industry information channel. More related knowledge is waiting for you to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report