Spring Security (new version) implements authority authentication and authorization

1. Introduction to Spring Security

1. Introduction to Spring Security

Spring is a very popular and successful Java application development framework, and Spring Security is a member of the Spring family. Based on the Spring framework, Spring Security provides a complete solution for web application security.

As you may know, the two core functions of security are authentication and authorization . Generally speaking, the security of web applications includes two parts: **User Authentication (Authentication) and User Authorization (Authorization)**. These two points It is also an important core function of Spring Security.

(1) User authentication refers to: verifying whether a user is a legitimate subject in the system, that is to say, whether the user can access the system. User authentication generally requires the user to provide a user name and password, and the system completes the authentication process by verifying the user name and password.

In layman's terms, it means whether the system thinks the user can log in

(2) User authorization refers to verifying whether a user has permission to perform an operation. In a system, different users have different permissions. For example, for a file, some users can only read it, while others can modify it. Generally speaking, the system assigns different roles to different users, and each role corresponds to a series of permissions.

In layman's terms, the system judges whether the user has permission to do certain things.

2. History

"Spring Security started late 2003," "Spring's acegi security system". The cause was a question on the Spring developers mailing list, someone asked whether to consider providing a Spring-based security implementation.

Spring Security started in late 2013 under the name "The Acegi Secutity System for Spring". A question was submitted to the Spring developers mailing list asking if there had been an opportunity for Spring's security community to consider an implementation. The Spring community was relatively small back then (compared to now). In fact Spring itself was only a project that existed on CourseForge in 2013, and the answer to this question is an area worthy of research, although the current lack of time prevents our exploration of it.

With this in mind, a simple security implementation was built but not released. A few weeks later, other members of the Spring community asked about security, and this time the code was sent to them. Several other requests followed. About 200,000 people had used the code by January 2014. These entrepreneurial individuals proposed a SourceForge project to join, which was officially launched in March 2004.

In the early days, this project did not have any authentication module of its own, and the authentication process relied on container-managed security and Acegi security. Instead of focusing on authorization. This worked fine at first, but more and more users requested additional container support. The fundamental limitations of container-specific authentication domain interfaces become clear. There is also a related issue adding paths to new containers, which is a common source of end-user confusion and misconfiguration.

Introduction to Acegi security-specific authentication services. About a year later, Acegi Security officially became a sub-project of the Spring Framework. The 1.0.0 final version was published in 2006 - after more than two and a half years of high-volume production software projects and hundreds of improvements and contributions from the active community.

Acegi Security officially became a Spring portfolio project at the end of 2007, renamed "Spring Security".

3. Comparison of the same product

3.1、Spring Security

Part of the Spring technology stack.
Link: SpringSecurity official website

Secure your applications by providing full and scalable authentication and authorization support.

Spring Security features:

⚫ Seamless integration with Spring.

⚫ Comprehensive permission control.

⚫ Designed specifically for web development.

​ ◼Old versions cannot be used without the Web environment.

​ ◼The new version extracts the entire framework hierarchically and divides it into core modules and Web modules. Introducing the core module alone can break away from the Web environment.

⚫ Heavyweight.

3.2、 Shiro

Apache's lightweight permission control framework.

Features:

⚫ Lightweight. The philosophy advocated by Shiro is to make complex things simple. For higher performance requirements

Internet applications have better performance.

⚫ Versatility.

​ ◼Benefits: Not limited to the Web environment, it can be used without the Web environment.

​ ◼ Defect: Some specific requirements in the Web environment require manual code customization.

Spring Security is a security management framework in the Spring family. In fact, Spring Security has been developed for many years before Spring Boot appeared, but it is not used much. The field of security management has always been dominated by Shiro.

Compared with Shiro, it is more troublesome to integrate Spring Security in SSM. Therefore, although Spring Security is more powerful than Shiro, it is not used as much as Shiro (although Shiro does not have as many functions as Spring Security, but for most projects, Shiro will also suffice).

Since Spring Boot is available, Spring Boot provides an automatic configuration solution for Spring Security, and Spring Security can be used with less configuration.

Therefore, in general, the combination of common security management technology stacks is as follows:

• SSM + Shiro

• Spring Boot/Spring Cloud + Spring Security

The above is just a recommended combination. From a purely technical point of view, no matter how you combine it, it can be run.

2. Spring Security implements permissions

To protect Web resources, the best way is Filter.
To protect method calls, the best way is [AOP]

When Spring Security performs authentication and authentication, it uses a series of Filters to intercept.
SpringSecurity filter chain

As shown in the figure, a request to access the API will go through the filters in the blue line box from left to right, where the green part is the filter responsible for authentication, the blue part is responsible for exception handling, and the orange part is responsible for authorized . After a series of interceptions, we finally accessed our API.

Here we only need to focus on two filters: UsernamePasswordAuthenticationFilterresponsible for login authentication and FilterSecurityInterceptorresponsible for authority authorization.

Explanation: The core logic of Spring Security is all in this set of filters, which will call various components to complete functions. If you master these filters and components, you will master Spring Security ! The way the framework is used is to extend these filters and components.

1. Getting Started with Spring Security

1.1 Add dependencies

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

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

Description: After the dependency package (spring-boot-starter-security) is imported, Spring Security provides many functions by default to protect the entire application:

1. Require authenticated users to interact with the application

2. Create a default login form

3. Generate usera random password for the username and print it on the console

4. CSRFAttack protection, Session Fixationattack protection

5. Wait, wait, wait...

1.2. Start the project test

Access in browser: http://localhost:8800/doc.html

Automatically redirected to the login page

insert image description here

Default username: user

The password will be printed on the console when the project is started. Note that the password will change every time it is started!
insert image description here

Enter the user name and password, successfully access the controller method and return data, indicating that the default security protection of Spring Security is in effect.

In actual development, these default configurations cannot meet our needs. We need to extend Spring Security components to complete custom configurations to meet our project requirements.

2. User authentication

User Authentication Process:
insert image description here
A Quick Look at Concepts:

** Authentication**Interface: Its implementation class represents the user currently accessing the system and encapsulates user-related information.

** AuthenticationManager**Interface: defines the method of Authentication Authentication

** UserDetailsService**Interface: The core interface for loading user-specific data. It defines a method to query user information based on username.

** UserDetails**Interface: Provide core user information. The user information obtained and processed through the UserDetailsService according to the username should be encapsulated into a UserDetails object and returned. Then encapsulate this information into the Authentication object.

2.1. User Authentication Core Components

There will be many users in our system, and confirming which user is currently using our system is the ultimate purpose of login authentication. Here we have extracted a core concept: currently logged in user/currently authenticated user . The security of the entire system revolves around the current logged-in user. This is not difficult to understand. If the current logged-in user cannot confirm, then A places an order and it is placed on B's account, which is not a mess. The embodiment of this concept in Spring Security is that Authenticationit stores authentication information representing the currently logged in user.

How do we obtain and use it in the program? We need to get it SecurityContextthrough Authentication, SecurityContextwhich is our context object! This context object is SecurityContextHoldermanaged and you can use it anywhere in the program:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

SecurityContextHolderThe principle is very simple, it is used ThreadLocalto ensure that the same object is passed in one thread!

Now we know the three core components in Spring Security:

​ 1. Authentication: Stores authentication information, representing the currently logged in user

​ 2. SeucirtyContext: Context object, used to getAuthentication

​ 3. SecurityContextHolder: Context management object, used to get anywhere in the programSecurityContext

AuthenticationWhat information is in:

​ 1. Principal: User information, usually the user name without authentication, and usually the user object after authentication

2. Credentials: User credentials, usually a password

3. Authorities: User permissions

2.2. User authentication

How does Spring Security perform user authentication?

AuthenticationManagerIt is the component used by Spring Security to perform authentication. You only need to call its authenticatemethod to complete the authentication. The default authentication method of Spring Security is UsernamePasswordAuthenticationFilterauthenticated in this filter, which is responsible for the authentication logic.

The key code for Spring Security user authentication is as follows:

// 生成一个包含账号密码的认证信息
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, passwrod);
// AuthenticationManager校验这个认证信息,返回一个已认证的Authentication
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 将返回的Authentication存到上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);

Let's analyze it below.

2.2.1. Authentication interface analysis

AuthenticationManagerThe validation logic is very simple:

According to the user name, the user object is first queried (throws an exception if it is not found), and the password of the user object is verified with the passed password. If the password does not match, an exception is thrown.

There is nothing to say about this logic, it couldn't be simpler. The point is that Spring Security provides components for each step here:

1. Who executes the logic of querying the user object based on the user name? User object data can be stored in memory, in files, or in databases, and you have to determine how to check it. This part is handled by ** UserDetialsService**. This interface has only one method loadUserByUsername(String username), which is to query the user object by username. The default implementation is to query in memory.

2. What is the user object that is queried? The user object data in each system is different, we need to confirm what our user data is like. The user data in Spring Security is UserDetailsrepresented by ** **, which provides common attributes such as account number and password.

3. You may think it is relatively simple to verify the password.if、else If it is done, there is no need to use any components, right? But after all, the framework is a more comprehensive framework. In addition to if、elsesolving the problem of password encryption, this component is ** PasswordEncoder**, which is responsible for password encryption and verification.

We can look at AuthenticationManagerthe approximate source code of the verification logic:

We can look at AuthenticationManagerthe approximate source code of the verification logic:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
    
...省略其他代码

    // 传递过来的用户名
    String username = authentication.getName();
    // 调用UserDetailService的方法,通过用户名查询出用户对象UserDetail(查询不出来UserDetailService则会抛出异常)
    UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(username);
    String presentedPassword = authentication.getCredentials().toString();

    // 传递过来的密码
    String password = authentication.getCredentials().toString();
    // 使用密码解析器PasswordEncoder传递过来的密码是否和真实的用户密码匹配
    if (!passwordEncoder.matches(password, userDetails.getPassword())) {
    
    
        // 密码错误则抛出异常
        throw new BadCredentialsException("错误信息...");
    }

    // 注意哦,这里返回的已认证Authentication,是将整个UserDetails放进去充当Principal
    UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails,
            authentication.getCredentials(), userDetails.getAuthorities());
    return result;

...省略其他代码
}

UserDetialsService, UserDetails, PasswordEncoder, these three components, Spring Security, have default implementations, which generally cannot meet our actual needs, so here we implement these components ourselves!

2.2.2, Encryptor PasswordEncoder

Adopt MD5 encryption
Custom encryption processing component: CustomMd5PasswordEncoder

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.util.Arrays;

/**
 * 自定义security密码校验
 * @author 尹稳健~
 * @version 1.0
 * @time 2023/1/31
 */
public class CustomMd5PasswordEncoder implements PasswordEncoder {
    
    
    @Override
    public String encode(CharSequence rawPassword) {
    
    
        // 进行一个md5加密
        return Arrays.toString(DigestUtils.md5Digest(rawPassword.toString().getBytes()));
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
    
    
        // 通过md5校验
        return encodedPassword.equals(Arrays.toString(DigestUtils.md5Digest(rawPassword.toString().getBytes())));
    }
}

2.2.3, user object UserDetails

This interface is what we call the user object, which provides some general attributes of the user. The source code is as follows:

public interface UserDetails extends Serializable {
    
    
	/**
     * 用户权限集合(这个权限对象现在不管它,到权限时我会讲解)
     */
    Collection<? extends GrantedAuthority> getAuthorities();
    /**
     * 用户密码
     */
    String getPassword();
    /**
     * 用户名
     */
    String getUsername();
    /**
     * 用户没过期返回true,反之则false
     */
    boolean isAccountNonExpired();
    /**
     * 用户没锁定返回true,反之则false
     */
    boolean isAccountNonLocked();
    /**
     * 用户凭据(通常为密码)没过期返回true,反之则false
     */
    boolean isCredentialsNonExpired();
    /**
     * 用户是启用状态返回true,反之则false
     */
    boolean isEnabled();
}

In actual development, our user attributes are various, and these default attributes may not be satisfied, so we generally implement this interface by ourselves, and then set up our actual user entity object. It is troublesome to rewrite many methods to implement this interface. We can inherit the class provided by Spring Security org.springframework.security.core.userdetails.User, which implements UserDetailsthe interface and saves us the work of rewriting methods:

import com.sky.model.system.SysUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

/**
 * 自定义对象
 * @author 尹稳健~
 * @version 1.0
 * @time 2023/1/31
 */
public class CustomUser extends User {
    
    
    private SysUser sysUser;

    public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
    
    
        super(sysUser.getUsername(), sysUser.getPassword(), authorities);
        this.sysUser = sysUser;
    }

    public SysUser getSysUser() {
    
    
        return sysUser;
    }

    public void setSysUser(SysUser sysUser) {
    
    
        this.sysUser = sysUser;
    }
}

2.2.4, business object UserDetailsService

The interface is simple with only one method:

public interface UserDetailsService {
    
    
    /**
     * 根据用户名获取用户对象(获取不到直接抛异常)
     */
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

We implement this interface and complete our business

import com.sky.model.system.SysUser;
import com.sky.system.custom.CustomUser;
import com.sky.system.service.SysUserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Objects;

/**
 * 实现UserDetailsService接口,重写方法
 * @author 尹稳健~
 * @version 1.0
 * @time 2023/1/31
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    

    @Resource
    private SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        SysUser sysUser = sysUserService.queryByUsername(username);
        if (Objects.isNull(sysUser)){
    
    
            throw new UsernameNotFoundException("用户名不存在!");
        }

        if(sysUser.getStatus() == 0) {
    
    
            throw new RuntimeException("账号已停用");
        }
        return new CustomUser(sysUser, Collections.emptyList());
    }
}

2.2.5. Login interface

Next, we need to customize the login interface, and then let Spring Security release this interface, so that users can access this interface without logging in.

​ In the interface, we use the authenticate method of AuthenticationManager to perform user authentication, so we need to configure in SecurityConfig to inject AuthenticationManager into the container.

​ If the authentication is successful, a jwt should be generated and returned in the response.

@Slf4j
@Api(tags = "系统管理-登录管理")
@RequestMapping("/admin/system/index")
@RestController
public class IndexController {
    
    

    @Resource
    private SysUserService sysUserService;
    
    @ApiOperation("登录接口")
    @PostMapping("/login")
    public Result<Map<String,Object>> login(@RequestBody LoginVo loginVo){
    
    
    return sysUserService.login(loginVo);
	}
}
2.2.6、 SecurityConfig配置
package com.sky.system.config;

import com.sky.system.custom.CustomMd5PasswordEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

/**
 * Security配置类
 * @author 尹稳健~
 * @version 1.0
 * @time 2023/1/31
 */
@Configuration
/**
 * @EnableWebSecurity是开启SpringSecurity的默认行为
 */
@EnableWebSecurity
public class SecurityConfig {
    
    

    /**
     * 密码明文加密方式配置
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
    
    
        return new CustomMd5PasswordEncoder();
    }

    /**
     * 获取AuthenticationManager(认证管理器),登录时认证使用
     * @param authenticationConfiguration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    
    
        return authenticationConfiguration.getAuthenticationManager();
    }


    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    
    
        return  http
                // 基于 token,不需要 csrf
                .csrf().disable()
                // 开启跨域以便前端调用接口
                .cors().and()
                .authorizeRequests()
                // 指定某些接口不需要通过验证即可访问。登录接口肯定是不需要认证的
                .antMatchers("/admin/system/index/login").permitAll()
                // 静态资源,可匿名访问
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**","/doc.html").permitAll()
                // 这里意思是其它所有接口需要认证才能访问
                .anyRequest().authenticated()
                .and()
                // 基于 token,不需要 session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // cors security 解决方案
                .cors().configurationSource(corsConfigurationSource())
                .and()
                .build();
    }

    /**
     * 配置跨源访问(CORS)
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
    
    
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        configuration.setAllowedMethods(Collections.singletonList("*"));
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}

The controller calls the actual business through the login method

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
    
    


    @Resource
    private SysMenuService sysMenuService;

    /**
     * 通过AuthenticationManager的authenticate方法来进行用户认证,
     */
    @Resource
    private AuthenticationManager authenticationManager;
    
	@Override
    public Result<Map<String, Object>> login(LoginVo loginVo) {
    
    
        // 将表单数据封装到 UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
        // authenticate方法会调用loadUserByUsername
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        if(Objects.isNull(authenticate)){
    
    
            throw new RuntimeException("用户名或密码错误");
        }
        // 校验成功,强转对象
        CustomUser customUser = (CustomUser) authenticate.getPrincipal();
        SysUser sysUser = customUser.getSysUser();
        // 校验通过返回token
        String token = JwtUtil.createToken(sysUser.getId(), sysUser.getUsername());
        Map<String, Object> map = new HashMap<>();
        map.put("token",token);
        return Result.ok(map);
    }
}
2.2.7, authentication filter

We need to customize a filter, which will obtain the token in the request header, parse the token to extract the information, and obtain the corresponding LoginUser object. Then encapsulate the Authentication object and store it in SecurityContextHolder.

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
    

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
    
    
            //放行
            filterChain.doFilter(request, response);
            return;
        }
        //解析token
        String userid;
        try {
    
    
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
    
    
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //从redis中获取用户信息
        String redisKey = "login:" + userid;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){
    
    
            throw new RuntimeException("用户未登录");
        }
        //存入SecurityContextHolder
        //TODO 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

3. User authorization

In Spring Security, the default FilterSecurityInterceptor is used for permission verification. In the FilterSecurityInterceptor, the Authentication will be obtained from the SecurityContextHolder , and then the permission information in it will be obtained. Determine whether the current user has the required permissions to access the current resource.

Authentication class in Spring Security:

public interface Authentication extends Principal, Serializable {
    
    
	//权限数据列表
    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

When the loadUserByUsername method is executed during login, return new CustomUser(sysUser, Collections.emptyList()); the following empty data connection is the permission data returned to Spring Security.

How to get permission data in TokenAuthenticationFilter? When logging in, we save the permission data in redis (the user name is key, and the permission data is value), so that the permission data can be obtained by obtaining the user name through token, so that a complete Authentication object can be formed.

3.1. Modify the loadUserByUsername interface method

@Autowired
private SysMenuService sysMenuService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
    SysUser sysUser = sysUserService.getByUsername(username);
    if(null == sysUser) {
    
    
        throw new UsernameNotFoundException("用户名不存在!");
    }

    if(sysUser.getStatus().intValue() == 0) {
    
    
        throw new RuntimeException("账号已停用");
    }
    List<String> userPermsList = sysMenuService.findUserPermsList(sysUser.getId());
    List<SimpleGrantedAuthority> authorities = new ArrayList<>();
    for (String perm : userPermsList) {
    
    
        authorities.add(new SimpleGrantedAuthority(perm.trim()));
    }
    return new CustomUser(sysUser, authorities);
}

3.2, modify the configuration class

Modify the WebSecurityConfig class

Add annotations to the configuration class:

Enable the method-based security authentication mechanism, that is to say, enable the security confirmation of the annotation mechanism in the controller of the web layer

@EnableGlobalMethodSecurity(prePostEnabled = true)

3.3. Control the controller layer interface permissions

Spring Security disables annotations by default. To enable annotations, you need to add the @EnableGlobalMethodSecurity annotation to the class that inherits WebSecurityConfigurerAdapter to determine whether the user has access to a method of a control layer.

Control the interface permissions of the controller layer through the @PreAuthorize tag

public class SysRoleController {
    
    

    @Autowired
    private SysRoleService sysRoleService;

    @PreAuthorize("hasAuthority('bnt.sysRole.list')")
    @ApiOperation(value = "获取分页列表")
    @GetMapping("/{page}/{limit}")
    public Result index(
            @ApiParam(name = "page", value = "当前页码", required = true)
            @PathVariable Long page,

            @ApiParam(name = "limit", value = "每页记录数", required = true)
            @PathVariable Long limit,

            @ApiParam(name = "roleQueryVo", value = "查询对象", required = false)
                    SysRoleQueryVo roleQueryVo) {
    
    
        Page<SysRole> pageParam = new Page<>(page, limit);
        IPage<SysRole> pageModel = sysRoleService.selectPage(pageParam, roleQueryVo);
        return Result.ok(pageModel);
    }


    ...
}

3.4. Test server-side permissions

Log in to the background and assign permissions for testing. If the page has button permission control, it can be temporarily removed to facilitate testing.

Test conclusion:

1. Those assigned permissions can successfully return interface data

2. An exception will be thrown if there is no assigned permission: org.springframework.security.access.AccessDeniedException: Access is not allowed

4. Exception handling

We also hope that in the case of authentication failure or authorization failure, we can also return the same structure of json as our interface, so that the front end can uniformly process the response. To achieve this function we need to know Spring Security's exception handling mechanism.

​ In Spring Security, if we have an exception during the authentication or authorization process, it will be caught by ExceptionTranslationFilter. In ExceptionTranslationFilter, it will be judged whether the authentication failed or the authorization failed.

​ If an exception occurs during the authentication process, it will be encapsulated into AuthenticationException and then the method of the AuthenticationEntryPoint object will be called to handle the exception.

​ If an exception occurs during the authorization process, it will be encapsulated into AccessDeniedException and then the method of the AccessDeniedHandler object will be called to handle the exception.

​ So if we need to customize exception handling, we only need to customize AuthenticationEntryPoint and AccessDeniedHandler and configure them to SpringSecurity.

There are 2 ways to handle exceptions:

1. Extend Spring Security exception handling classes: AccessDeniedHandler, AuthenticationEntryPoint

2. Unified processing of global exceptions in spring boot

Description of the first solution: If the system implements global exception handling, then the global exception will first get the AccessDeniedException exception. If the Spring Security extension exception takes effect, the exception must be thrown again in the global exception.

①Custom implementation class

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sky.common.result.ResultCodeEnum;
import com.sky.common.util.WebUtils;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 认证失败处理
 * @author 尹稳健~
 * @version 1.0
 * @time 2023/2/1
 */
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    
    
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    
    
        response.setStatus(200);
        int code = ResultCodeEnum.LOGIN_AUTH.getCode();
        String msg = "认证失败,无法访问系统资源";
        response.setContentType("application/json;charset=UTF-8");
        Map<String, Object> result = new HashMap<>();
        result.put("msg", msg);
        result.put("code", code);
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sky.common.result.ResultCodeEnum;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 尹稳健~
 * @version 1.0
 * @time 2023/2/1
 */
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    
    
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    
    
        int code = ResultCodeEnum.PERMISSION.getCode();
        response.setStatus(200);
        response.setContentType("application/json;charset=UTF-8");
        String msg = "权限不足,无法访问系统资源";
        Map<String, Object> result = new HashMap<>();
        result.put("msg", msg);
        result.put("code", code);
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}


② Configure Spring Security

​ First inject the corresponding processor

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

​ Then we can use the methods of the HttpSecurity object to configure.

        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).
                accessDeniedHandler(accessDeniedHandler);

Guess you like

Origin blog.csdn.net/weixin_46073538/article/details/128641746