Don't let your microservice run naked anymore. Based on Spring Session & Spring Security microservice permission control

Posted by spudly on Tue, 29 Oct 2019 17:18:57 +0100

Microservice architecture

  • Gateway: route the user's request to the specified service and forward the Session information contained in the front-end Cookie;
  • User service: user login Authentication, user authorization, and Redis Session Management
  • Other services: rely on user information in Redis for interface request verification

Structure design of user role permission table

  • Permission table
    The minimum granularity of permission table controls a single function, such as user management, resource management, table structure example:
id authority description
1 ROLE_ADMIN_USER Manage all users
2 ROLE_ADMIN_RESOURCE Manage all resources
3 ROLE_A_1 Access to an interface of ServiceA
4 ROLE_A_2 Access to another interface of ServiceA
5 ROLE_B_1 Access to an interface of ServiceB
6 ROLE_B_2 Access to another interface of ServiceB
  • Role permission table
    Customize roles and combine various permissions. For example, super administrator has all permissions. Table structure example:
id name authority_ids
1 Super administrator 1,2,3,4,5,6
2 Administrator A 3,4
3 Administrator B 5,6
4 Ordinary users NULL
  • User role table
    The user binds one or more roles, i.e. assigns various permissions. Example table structure:
user_id role_id
1 1
1 4
2 2

User service design

Maven dependency (all services)

 <!-- Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- Spring Session Redis -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

Application configuration application.yml example:

# Spring Session configuration
spring.session.store-type=redis
server.servlet.session.persistent=true
server.servlet.session.timeout=7d
server.servlet.session.cookie.max-age=7d

# Redis configuration
spring.redis.host=<redis-host>
spring.redis.port=6379

# MySQL configuration
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://<mysql-host>:3306/test
spring.datasource.username=<username>
spring.datasource.password=<passowrd>

authentication and authorization of user login

Slf4j
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private final UserService userService;

    CustomAuthenticationFilter(String defaultFilterProcessesUrl, UserService userService) {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl, HttpMethod.POST.name()));
        this.userService = userService;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        JSONObject requestBody = getRequestBody(request);
        String username = requestBody.getString("username");
        String password = requestBody.getString("password");
        UserDO user = userService.getByUsername(username);
        if (user != null && validateUsernameAndPassword(username, password, user)){
            // Query the user's authority
            List<SimpleGrantedAuthority> userAuthorities = userService.getSimpleGrantedAuthority(user.getId());
            return new UsernamePasswordAuthenticationToken(user.getId(), null, userAuthorities);
        }
        throw new AuthenticationServiceException("Login failed");
    }

    /**
     * Get request body
     */
    private JSONObject getRequestBody(HttpServletRequest request) throws AuthenticationException{
        try {
            StringBuilder stringBuilder = new StringBuilder();
            InputStream inputStream = request.getInputStream();
            byte[] bs = new byte[StreamUtils.BUFFER_SIZE];
            int len;
            while ((len = inputStream.read(bs)) != -1) {
                stringBuilder.append(new String(bs, 0, len));
            }
            return JSON.parseObject(stringBuilder.toString());
        } catch (IOException e) {
            log.error("get request body error.");
        }
        throw new AuthenticationServiceException(HttpRequestStatusEnum.INVALID_REQUEST.getMessage());
    }

    /**
     * Verify user name and password
     */
    private boolean validateUsernameAndPassword(String username, String password, UserDO user) throws AuthenticationException {
         return username == user.getUsername() && password == user.getPassword();
    }

}

@EnableWebSecurity
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String LOGIN_URL = "/user/login";

    private static final String LOGOUT_URL = "/user/logout";

    private final UserService userService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(LOGIN_URL).permitAll()
                .anyRequest().authenticated()
                .and()
                .logout().logoutUrl(LOGOUT_URL).clearAuthentication(true).permitAll()
                .and()
                .csrf().disable();

        http.addFilterAt(bipAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .rememberMe().alwaysRemember(true);
    }

    /**
     * Custom authentication filter
     */
    private CustomAuthenticationFilter customAuthenticationFilter() {
        CustomAuthenticationFilter authenticationFilter = new CustomAuthenticationFilter(LOGIN_URL, userService);
        return authenticationFilter;
    }

}

Other service design

Application configuration application.yml example:

# Spring Session configuration
spring.session.store-type=redis

# Redis configuration
spring.redis.host=<redis-host>
spring.redis.port=6379

Global security configuration

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
    }

}

Access to user authentication information

After the user logs in successfully through the user service, the user information will be cached to Redis. The cached information is related to the object returned by the attemptAuthentication() method in the CustomAuthenticationFilter. As mentioned above, the returned object is the new usernamepasswordauthenticationtoken (user. Getid(), null, user authorities), that is, Redis caches the user's ID and user's authority (a Hotels).

The first parameter of the UsernamePasswordAuthenticationToken constructor is the Object object Object, so you can customize the cache Object.

The method to obtain the user's information in each module of the microservice is as follows:

@GetMapping()
    public WebResponse test(@AuthenticationPrincipal UsernamePasswordAuthenticationToken authenticationToken){
       // slightly
    }

Authority control

  • Enable method based permission annotation
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
  • Simple permission verification
    For example, to delete the interface of a role, only users with the role admin user permission are allowed to access it.
/**
     * Delete roles
     */
    @PostMapping("/delete")
    @PreAuthorize("hasRole('ADMIN_USER')")
    public WebResponse deleteRole(@RequestBody RoleBean roleBean){
          // slightly
    }

@Preauthorize ("hasrole ('< authority >')) can act on various modules in the microservice

  • User defined permission verification
    As shown above, the hasRole() method is embedded in Spring Security. To customize it, you can use expression based access control. For example:
/**
 * Custom verification service
 */
@Service
public class CustomService{

    public boolean check(UsernamePasswordAuthenticationToken authenticationToken, String extraParam){
          // slightly
    }

}

/**
     * Delete roles
     */
    @PostMapping()
    @PreAuthorize("@customService.check(authentication, #userBean.username)")
    public WebResponse custom(@RequestBody UserBean userBean){
          // slightly
    }

authentication belongs to the built-in object. Get the value of the input parameter.

  • Dynamic modification of any user authority
    In principle, the user's permission information is saved in Redis. To modify the user's permission, you need to operate Redis. For example:
@Service
@AllArgsConstructor
public class HttpSessionService<S extends Session>  {

    private final FindByIndexNameSessionRepository<S> sessionRepository;

    /**
     * Reset user rights
     */
    public void resetAuthorities(Long userId, List<GrantedAuthority> authorities){
        UsernamePasswordAuthenticationToken newToken = new UsernamePasswordAuthenticationToken(userId, null, authorities);
        Map<String, S> redisSessionMap = sessionRepository.findByPrincipalName(String.valueOf(userId));
        redisSessionMap.values().forEach(session -> {
            SecurityContextImpl securityContext = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
            securityContext.setAuthentication(newToken);
            session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext);
            sessionRepository.save(session);
        });
    }

}

To modify the user permissions, you only need to call httpSessionService.resetAuthorities() method, which takes effect in real time.

Copyright belongs to the author. Please contact the author for reprint or content cooperation.

Spring Cloud Gateway - quick start

APM tool looks around and finds SkyWalking is my true love

Spring Boot injects static variables from external configuration to internal application

Convert HTML to PDF new pose

Java uses UnixSocket to call Docker API

Fastjson fatal defect

Service Mesh - gRPC local joint debugging remote service

Dynamically rendering HTML with Thymeleaf

Fastjson fatal defect

Spring Boot 2 integrated log4j2 log framework

Java interview key points summary core chapter reference answer

The framework of Java interview key points summary

Spring Security: how to protect user password

Spring Boot RabbitMQ - priority queue

Original link: https://mp.weixin.qq.com/s? Biz = mzu0mdewmjwwna = & mid = 2247486167 & IDX = 2 & Sn = 76dba01d116b7147c9b1dfb7cbf2d8d28d228 & chksm = fb3f132ccc4489a3a3a3a2053148323d 660c440e8af90dcd33580042289958f98b44a258d223badba8 & token = 280305379 & lang = en ABCD CN Rd

This article is based on the platform of blog one article multiple sending OpenWrite Release!

Topics: Java Spring Redis Session MySQL