1
头图

Hello everyone, I am not a

Authentication and authorization are indispensable parts of actual combat projects, and Spring Security will be the preferred security component, so Chen "Spring Security Advanced" , and wrote about the monolithic architecture to OAuth2 authentication and authorization of distributed architecture.

Spring security will not be introduced too much here. I believe everyone has used it and feared it. Compared with Shiro, Spring Security is more heavyweight. The previous SSM projects are more companies using Shiro, but Spring Boot After it came out, it was more convenient to integrate Spring Security, and more companies were used.

Today, Chen will introduce how to use Spring Security for login authentication in a project where the front and back ends are separated. The list of articles is as follows:

The idea of separating front-end and back-end authentication

The separation of front-end and back-end is different from traditional web services, and sessions cannot be used. Therefore, we use the stateless mechanism of JWT to generate tokens. The general idea is as follows:

  1. The client calls the server login interface, enters the user name and password to log in, and the login successfully returns two token , as follows:

    1. accessToken : The client carries this token to access the resources of the server
    2. refreshToken : refresh token, once the accessToken expires, the client needs to use refreshToken to obtain an accessToken again. Therefore, the expiration time of refreshToken is generally greater than that of accessToken.
  2. The client request header carries the accessToken access the resources of the server. The server authenticates the accessToken (verification, whether it is invalid....), and 161a9ffca9ce62 if there is no problem.
  3. accessToken expires, the client needs to carry refreshToken call the refresh token interface to obtain a new accessToken .

Project construction

Chen uses the Spring Boot framework, and the demo project has created two new modules, namely common-base and security-authentication-jwt .

1, common-base module

This is an abstracted public module, this module mainly puts some public classes, the directory is as follows:

2, security-authentication-jwt module

Some classes that need to be customized, such as the global configuration class of security and the configuration class of Jwt login filter, are listed as follows:

3. Five tables

Permission design often has different designs according to business needs. Chen used the RBAC specification, which mainly involves five tables, namely user table , role table , Role table , role<->Permission table , as shown below:

The SQL of the above tables will be placed in the case source code (the fields of these tables are not designed to save trouble, so you can expand it gradually according to your business)

Login authentication filter

There are many logical ways to write the login interface. Today, Chen introduces a login interface that uses a filter definition.

Spring Security's default form login authentication filter is UsernamePasswordAuthenticationFilter , this filter is not suitable for the architecture of separation of front and back ends, so we need to customize a filter.

The logic is very simple, refer to UsernamePasswordAuthenticationFilter to transform it, the code is as follows:

Authentication success handler AuthenticationSuccessHandler

Once the above filter interface is successfully authenticated, it will call AuthenticationSuccessHandler for processing, so we can customize an authentication success handler to perform our own business processing, the code is as follows:

Chen only returned accessToken , refreshToken , and other business logic was processed by itself.

AuthenticationFailureHandler

Similarly, once the login fails, such as the user name or password is wrong, etc., it will call AuthenticationFailureHandler for processing, so we need to customize an authentication failure handler, which returns specific JSON data to the customer based on the exception information At the end, the code is as follows:

The logic is very simple, AuthenticationException has different implementation classes, and you can return specific prompt information according to the type of exception.

AuthenticationEntryPoint configuration

AuthenticationEntryPoint this interface when user unauthorized access to protected resources by time, which will be called the commence() methods of treatment, such as the client carries token has been tampered with, so we need to customize a AuthenticationEntryPoint return specific Prompt information, the code is as follows:

AccessDeniedHandler configuration

AccessDeniedHandler This processor when the authenticated user accesses a protected resource, but the permission is not enough , it will enter this processor for processing, we can implement this processor to return specific prompt information to the client, the code is as follows:

UserDetailsService configuration

UserDetailsService This class is used to load user information, including user name , password , rights , role collection .... There is a follows:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

In the authentication logic, Spring Security will call this method to load the detailed information of the user according to the username passed in by the client. The logic that this method needs to complete is as follows:

  • Password match
  • Loading permissions, role collection

We need to implement this interface to database , the code is as follows:

Among them, LoginService is to query the password, role, and authority from the database according to the user name. The code is as follows:

UserDetails This is also an interface, which defines several methods, all around user name , password , permission + set of roles these three attributes, so we can achieve this kind expand these fields, The is as follows:

expand : UserDetailsService achieve this kind generally involves 5 tables, which are user table , role table , permission table , user <-> role correspondence table , role <-> Permission correspondence table , the implementation in the enterprise must follow the RBAC design rule. Chen will introduce this rule in detail later.

Token verification filter

The client request header carries the token. The server must parse and verify the token for each request. Therefore, a Token filter must be defined. The main logic of this filter is as follows:

  • accessToken from the request header
  • Analysis, verification, and expiration time of accessToken
  • After the verification is successful, the authentication is stored in ThreadLocal, which facilitates the subsequent direct access to user details.

The above is just some of the most basic logic. There are specific processing in actual development, such as putting the user's detailed information into the Request attribute and the Redis cache, so that the token relay effect of feign can be realized.

The code to verify the filter is as follows:

Refresh token interface

accessToken expires, the client must carry the refreshToken 161a9ffca9d30b to retrieve the token. The traditional web service is placed in a cookie, and only the server is required to complete the refresh, which is completely The client must hold the refreshToken adjust the interface to refresh manually.

code show as below:

The main logic is very simple, as follows:

  • Check refreshToken
  • Regenerate accessToken , refreshToken return it to the client.
Note: In actual production, the generation method and encryption algorithm refreshToken token can be different from accessToken

Login authentication filter interface configuration

The above defines an authentication filter JwtAuthenticationLoginFilter , this is a filter used to log in, but it is not injected into the filter chain of Spring Security, and the configuration needs to be defined. The code is as follows:

/**
 * @author 公号:码猿技术专栏
 * 登录过滤器的配置类
 */
@Configuration
public class JwtAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    /**
     * userDetailService
     */
    @Qualifier("jwtTokenUserDetailsService")
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 登录成功处理器
     */
    @Autowired
    private LoginAuthenticationSuccessHandler loginAuthenticationSuccessHandler;

    /**
     * 登录失败处理器
     */
    @Autowired
    private LoginAuthenticationFailureHandler loginAuthenticationFailureHandler;

    /**
     * 加密
     */
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 将登录接口的过滤器配置到过滤器链中
     * 1. 配置登录成功、失败处理器
     * 2. 配置自定义的userDetailService(从数据库中获取用户数据)
     * 3. 将自定义的过滤器配置到spring security的过滤器链中,配置在UsernamePasswordAuthenticationFilter之前
     * @param http
     */
    @Override
    public void configure(HttpSecurity http) {
        JwtAuthenticationLoginFilter filter = new JwtAuthenticationLoginFilter();
        filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        //认证成功处理器
        filter.setAuthenticationSuccessHandler(loginAuthenticationSuccessHandler);
        //认证失败处理器
        filter.setAuthenticationFailureHandler(loginAuthenticationFailureHandler);
        //直接使用DaoAuthenticationProvider
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        //设置userDetailService
        provider.setUserDetailsService(userDetailsService);
        //设置加密算法
        provider.setPasswordEncoder(passwordEncoder);
        http.authenticationProvider(provider);
        //将这个过滤器添加到UsernamePasswordAuthenticationFilter之前执行
        http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    }
}

All the logic is in public void configure(HttpSecurity http) , as follows:

  • Set authentication success handler loginAuthenticationSuccessHandler
  • Set authentication failure handler loginAuthenticationFailureHandler
  • Set the implementation class of 161a9ffca9d466
  • Set the encryption algorithm passwordEncoder
  • Add the JwtAuthenticationLoginFilter filter to the filter chain, directly before the UsernamePasswordAuthenticationFilter filter.

Spring Security global configuration

The above only configures the login filter, but also needs to do some configuration in the global configuration class, as follows:

  • Apply login filter configuration
  • Release the login interface and token refresh interface without interception
  • Configure AuthenticationEntryPoint , AccessDeniedHandler
  • Disable session, separation of front and back ends + JWT mode does not require session
  • Add the token verification filter TokenAuthenticationFilter to the filter chain and place it before UsernamePasswordAuthenticationFilter

The complete configuration is as follows:

/**
 * @author 公众号:码猿技术专栏
 * @EnableGlobalMethodSecurity 开启权限校验的注解
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig;
    @Autowired
    private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
    @Autowired
    private RequestAccessDeniedHandler requestAccessDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                //禁用表单登录,前后端分离用不上
                .disable()
                //应用登录过滤器的配置,配置分离
                .apply(jwtAuthenticationSecurityConfig)

                .and()
                // 设置URL的授权
                .authorizeRequests()
                // 这里需要将登录页面放行,permitAll()表示不再拦截,/login 登录的url,/refreshToken刷新token的url
                //TODO 此处正常项目中放行的url还有很多,比如swagger相关的url,druid的后台url,一些静态资源
                .antMatchers(   "/login","/refreshToken")
                .permitAll()
                //hasRole()表示需要指定的角色才能访问资源
                .antMatchers("/hello").hasRole("ADMIN")
                // anyRequest() 所有请求   authenticated() 必须被认证
                .anyRequest()
                .authenticated()

                //处理异常情况:认证失败和权限不足
                .and()
                .exceptionHandling()
                //认证未通过,不允许访问异常处理器
                .authenticationEntryPoint(entryPointUnauthorizedHandler)
                //认证通过,但是没权限处理器
                .accessDeniedHandler(requestAccessDeniedHandler)

                .and()
                //禁用session,JWT校验不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()
                //将TOKEN校验过滤器配置到过滤器链中,否则不生效,放到UsernamePasswordAuthenticationFilter之前
                .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
                // 关闭csrf
                .csrf().disable();
    }

    // 自定义的Jwt Token校验过滤器
    @Bean
    public TokenAuthenticationFilter authenticationTokenFilterBean()  {
        return new TokenAuthenticationFilter();
    }

    /**
     * 加密算法
     * @return
     */
    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

The annotations are very detailed. If you don't understand, take a look carefully.

The source code of the case has been uploaded to GitHub, follow the public : 161a9ffca9d5c8 Code Ape Technology Column , reply keywords: 9529 get it!

test

1. First test the login interface, postman visits http://localhost:2001/security-jwt/login, as follows:

As you can see, two tokens were returned successfully.

2. The request header does not carry token, directly request http://localhost:2001/security-jwt/hello, as follows:

As you can see, it directly enters the EntryPointUnauthorizedHandler processor.

http://localhost:2001/security-jwt/hello with the token

The token is valid for a successful visit.

4. Refresh the token interface test, carry an expired token to access as follows:

5. Refresh token interface test, carry unexpired token test, as follows:

As you can see, two new tokens were successfully returned.

Source code tracking

The above series of configurations are completely UsernamePasswordAuthenticationFilter 161a9ffca9d6ca, which is the web service form login method.

The principle of Spring Security is composed of a series of filters, and the login process is the same. Initially, authentication and matching were performed org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter()

attemptAuthentication() is to obtain the username and password passed by the client, encapsulate it into UsernamePasswordAuthenticationToken to ProviderManager for authentication. The source code is as follows:

The main process of ProviderManager is to call the abstract class AbstractUserDetailsAuthenticationProvider#authenticate() method, as shown below:

retrieveUser() method is to call userDetailService to query user information. Then the authentication, once the authentication succeeds or fails, the corresponding failure and success handlers will be called for processing.

Summarize

Although Spring Security is heavier, it is really easy to use, especially to implement the Oauth2.0 specification, which is very simple and convenient.

The case source code has been uploaded to GitHub, follow the public : 161a9ffca9d785 Code Ape Technology Column , reply keywords: 9529 get!

One last word

Chen every article is carefully output, he has written 3 Ge column , organized into PDF , acquired as follows:

  1. "Spring Cloud Advanced" PDF: Follow the public number: [ Code Ape Technology Column ] Reply to the keyword Spring Cloud Advanced get!
  2. "Spring Boot Advanced" PDF: Follow the public number: [ Code Ape Technology Column ] Reply to the keyword Spring Boot Advanced get!
  3. "Mybatis Advanced" PDF: Follow the : [161a9ffca9d847 Code Ape Technology Column ] Reply to the keyword Mybatis Advanced Get it!

If this article is helpful or enlightening to you, , see , forwarding , , the biggest motivation for you to keep on 161a9ffca9d86f, 161a9ffca9d870d, I keep on collecting 161a9!

attention to the public number: 161a9ffca9d884 [Code Ape Technology Column] , there are super fan benefits in the public plus group , you can join the technical discussion group, discuss technology with everyone, brag!


码猿技术专栏
486 声望108 粉丝