Project integration Spring Security

Preface

 The demo of the Tutu theater management system written earlier  is based on shiro authentication. After the front and back ends of the project are separated, it is obviously more convenient to integrate Spring Security. After all, Spring is used, and the authority management is of course Spring Security.

I spent half a day organizing notes, I hope it can be helpful to you.

One sentence overview of Spring Security: permission authentication composed of a set of filter chains.

1. Add dependency

Environment: The project uses Spring Initializr to quickly build Spring Boot, and the version is managed by  spring-boot-starter-parent  .

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

After only adding dependencies, start the project and see:

1.1 Console printing

The console prints a string of passwords, as shown in the figure below:

Visit one of the methods in the project:

http://localhost:7777/tmax/videoCategory/getAll

Strange, why did I jump to the /login path and let me log in?

1.2 Account login

Enter the following in the login from form:

  • Username: user
  • Password: 0839a4ba-c8a3-4aee-8a6e-cd19c1d0b0c1 (printed on the console)

Click Sign in and jump to the target address:

After adding the Spring Security dependency, two things were actually triggered. All the connection services in the system were protected for a while, and then there would be a default configuration form authentication.

2. Basic principles

The entire workflow of Spring Security is as follows:

The green authentication method can be configured, and the positions of orange and blue cannot be changed.

Security has two authentication methods:

  • httpbasic
  • The default formLogin, as above

Similarly, Security also provides two filter classes:

  • UsernamePasswordAuthenticationFilter represents the form login filter
  • BasicAuthenticationFilter means httpbaic login filter

The orange FilterSecurityInterceptor in the figure is the final filter, which determines whether the current request can access the Controller, and the judgment rules are placed in this.

When it fails, the exception will be thrown to the ExceptionTranslationFilter filter in front of this filter.

When ExceptionTranslationFilter receives the exception information, it will jump to the page to guide the user to authenticate, as shown in the user login interface shown above.

Three, custom authentication logic

In actual development, it is impossible to use the default method of Spring Security above. How to override the default configuration of Spring Security?

We take: changing the default form authentication method to httpbasic as an example.

Create SpringSecurity custom configuration class: WebSecurityConfig.java

@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
                .authorizeRequests();

        registry.and()
            表单登录方式
            .formLogin()
            .permitAll()
            .and()
            .logout()
            .permitAll()
            .and()
            .authorizeRequests()
            任何请求
            .anyRequest()
            需要身份认证
            .authenticated()
            .and()
            关闭跨站请求防护
            .csrf().disable()
            前后端分离采用JWT 不需要session
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    }
}

Restart the project and have seen the modified httpbasic authentication.

Here we still use the default username user provided and the password automatically generated every time the server starts. Can we customize the authentication logic? For example, use the user login in the database?

The answer is yes.

Custom user authentication logic requires three steps:

  1. Processing user information acquisition logic
  2. Processing user verification logic
  3. Process password encryption and decryption

Next, let's take a look at these three steps, and then implement custom login:

3.1 Processing user information acquisition logic

The acquisition logic of user information acquisition logic in Spring Security is encapsulated in an interface: UserDetailService, the code is as follows:

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

There is only one method in this interface, loadUserByUsername(), which receives a username parameter of type String and returns a UserDetails object.

So what exactly does this method do?

Pass the user name entered by the front-end user, and then go to the database storage to obtain the corresponding user information, and then encapsulate it in the UserDetail implementation class.

After encapsulated in the UserDetail implementation class and returned, Spring Srcurity will take the user information for verification. If the verification is passed, it will put the user in the session, otherwise, throw a UsernameNotFoundException exception, and Spring Security will make a corresponding response after capturing Prompt information.

To handle the user information acquisition logic, then we need to implement UserDetailsService ourselves

New UserDetailsServiceImpl.java

@Slf4j
@Component
public class UserDetailsServiceImpl implements UserDetailsService{

    @Autowired
    private UserService userService;

    /**
     * 从数据库中获取用户信息,返回一个 UserDetails 对象,
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        通过用户名获取用户
        User user = userService.findByUsername(username);
        将 user 对象转化为 UserDetails 对象
        return new SecurityUserDetails(user);
    }
}

SecurityUserDetail.java

public class SecurityUserDetails extends User implements UserDetails {

    private static final long serialVersionUID = 1L;

    public SecurityUserDetails(User user) {

        if(user!=null) {
            this.setUsername(user.getUsername());
            this.setPassword(user.getPassword());
            this.setStatus(user.getStatus());
        }
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        理想型返回 admin 权限,可自已处理这块
        return AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
    }

    /**
     * 账户是否过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 是否禁用
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 密码是否过期
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否启用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

At this point, the logic part of processing user information acquisition is completed, mainly implementing the loadUserByname method of the UserDetailsService interface.

Why is the SecurityUserDetail class used for conversion?

In fact, a User object can be returned directly, but it should be noted that if the User object is returned directly, the user under the security package is returned.

As for why this is done, if the user under the security package is returned, the meaning of using the local database is lost. The custom login logic is explained in detail below.

Let's log in and try again:

Among them, niceyoo, is the database user information, the following figure shows the successful jump:

3.2 Processing user verification logic

The user's verification logic mainly includes two aspects:

  1. Whether the password matches [Processed by Sprin Security, just tell its password]
  2. Whether the password has expired, or whether the account is frozen, etc.

The former has been implemented by implementing the loadUserByname() method of UserDetailsService. Next, let’s look at the latter.

Whether the user password expires, whether it is frozen, etc. need to implement the UserDetails interface:

public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();授权列表;

    String getPassword();从数据库中查询到的密码;

    String getUsername();用户输入的用户名;

    boolean isAccountNonExpired();当前账户是否过期;

    boolean isAccountNonLocked();账户是否被锁定;

    boolean isCredentialsNonExpired();账户的认证时间是否过期;

    boolean isEnabled();是账户是否有效。
}

Mainly look at the last four methods:

1. IsAccountNonExpired() If the account has not expired, return true to indicate that it has not expired
2. IsAccountNonLocked() The account is not locked
3. IsCredentialsNonExpired() Is the password expired
4. IsEnabled() is deleted

The above four methods can all be responded to according to the actual situation.

3.3 Processing password encryption and decryption

Go back to the WebSecurityConfig custom configuration class. Join:

@Autowired
private UserDetailsServiceImpl userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());//加密
}

After configuring this configure method, the password passed from the front end will be encrypted, so the password queried from the database must be encrypted, and this process is all encrypted when the user registers.

Supplement: UserDetailsServiceImpl is a custom UserDetailsService implementation class.

Four, personalized certification process

Similarly in actual development, it is impossible to use Spring Security's own methods or pages for user login authentication, and you need to customize the login process suitable for the project.

Spring Security supports users to configure their own login page in the configuration file. If the user configures it, the user's own page is used, otherwise the module's built-in login page is used.

Added success and failure filters to the WebSecurityConfig configuration class.

@Autowired
private AuthenticationSuccessHandler successHandler;

@Autowired
private AuthenticationFailHandler failHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {

    ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
            .authorizeRequests();

    registry.and()
        表单登录方式
        .formLogin()
        .permitAll()
        成功处理类
        .successHandler(successHandler)
        失败
        .failureHandler(failHandler)
        .and()
        .logout()
        .permitAll()
        .and()
        .authorizeRequests()
        任何请求
        .anyRequest()
        需要身份认证
        .authenticated()
        .and()
        关闭跨站请求防护
        .csrf().disable()
        前后端分离采用JWT 不需要session
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

After adding AuthenticationSuccessHandler and AuthenticationFailHandler, it will automatically guide the package for us, but since it is a personalized authentication process, we naturally have to implement it ourselves~

So what effect do we want to achieve?

Custom login successful processing:

Custom login failure handling:

Why adopt this new style of return?

After the user logs in successfully, Spring Security's default processing method is to jump to the original link, which is also a common method for enterprise-level development, but sometimes the request sent by Ajax is used, and Json data is often returned, as shown in the figure. : After successful login, the token will be returned to the foreground, and failure information will be returned when it fails.

AuthenticationSuccessHandler:

Slf4j
@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        String username = ((UserDetails)authentication.getPrincipal()).getUsername();
        List<GrantedAuthority> authorities = (List<GrantedAuthority>) ((UserDetails)authentication.getPrincipal()).getAuthorities();
        List<String> list = new ArrayList<>();
        for(GrantedAuthority g : authorities){
            list.add(g.getAuthority());
        }
        登陆成功生成token
        String  token = UUID.randomUUID().toString().replace("-", "");
    token 需要保存至服务器一份,实现方式:redis or jwt
        输出到浏览器
        ResponseUtil.out(response, ResponseUtil.resultMap(true,200,"登录成功", token));
    }
}

SavedRequestAwareAuthenticationSuccessHandle r is Spring Security's default success handler, and the default method is to jump. Here the authentication information is returned as Json data, and other data can also be returned. This is determined according to business requirements. For example, the code above will return a token after the user logs in successfully. It should be noted that this token needs to be backed up on the server One, after all, it should be used for the next identity verification~

AuthenticationFailHandler:

@Component
public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {

        ## 默认情况下,不管你是用户名不存在,密码错误,SS 都会报出 Bad credentials 异常信息
        if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
            ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"用户名或密码错误"));
        } else if (e instanceof DisabledException) {
            ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"账户被禁用,请联系管理员"));
        } else {
            ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"登录失败,其他内部错误"));
        }
    }

}

The failure handler is the same as the successful handling.

ResponseUtil:

@Slf4j
public class ResponseUtil {

    /**
     *  使用response输出JSON
     * @param response
     * @param resultMap
     */
    public static void out(HttpServletResponse response, Map<String, Object> resultMap){

        ServletOutputStream out = null;
        try {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=UTF-8");
            out = response.getOutputStream();
            out.write(new Gson().toJson(resultMap).getBytes());
        } catch (Exception e) {
            log.error(e + "输出JSON出错");
        } finally{
            if(out!=null){
                try {
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Which uses gson dependency:

<!-- Gson -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.5</version>
</dependency>

At last

The next article will integrate jwt to achieve user identity authentication.

Spring Security integrates JWT

Guess you like

Origin blog.csdn.net/suixinsuoyu12519/article/details/112789442
Recommended