Spring Security Custom Logon Authentication

I. Preface

This article will describe Spring Security's custom login authentication verification username and password, custom password encryption, and authentication failure or successful processing of returned json format data in the case of front-end and back-end separation.

Warm Tip: Spring Security has the default password encryption and login user authentication verification, but here we choose the customization to facilitate future business expansion, such as the default system with a super administrator, when the authentication is recognized as super administrator account login access to give it the highest privileges, access to all api interfaces of the system, or login recognition. After the certificate is successful, it is stored in token so that users can authenticate user rights through token when they access other interfaces of the system.

Previous articles can be used for introductory learning of Spring Security:

SpringBoot Integrated Spring Security Initial Experience (I)

https://blog.csdn.net/qq_38225558/article/details/101754743

2. Spring Security Custom Logon Authentication Processing

Basic environment
  1. spring-boot 2.1.8
  2. mybatis-plus 2.2.0
  3. mysql
  4. maven project

Database User Information Table t_sys_user

In the case of the t_sys_user user table related to the addition, deletion and modification of the code is not posted, if necessary, can refer to the case demo source code provided at the end of the article.

1. Security Core Configuration Class

Configure User Password Check Filter

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * User Password Check Filter
     */
    private final AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter;

    public SecurityConfig(AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter) {
        this.adminAuthenticationProcessingFilter = adminAuthenticationProcessingFilter;
    }

    /**
     * Permission configuration
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.antMatcher("/**").authorizeRequests();

        // Disable CSRF to open cross-domain
        http.csrf().disable().cors();

        // Login Processing - In the case of front-end and back-end integration
//        registry.and().formLogin().loginPage("/login").defaultSuccessUrl("/").permitAll()
//                // Custom login username and password attribute names, default to username and password
//                .usernameParameter("username").passwordParameter("password")
//                // exception handling
//                .failureUrl("/login/error").permitAll()
//                // Log out
//                .and().logout().permitAll();

        // Identity can only access `home'interface in server local ip[127.0.0.1 or localhost], other ip addresses can't.
        registry.antMatchers("/home").hasIpAddress("127.0.0.1");
        // Allow anonymous url - can be understood as a release interface - multiple interfaces used, partitioned
        registry.antMatchers("/login", "/index").permitAll();
        // OPTIONS (Options): Find communication options for a specific Web site resource. Allow clients to determine resource-related options and/or requirements, or the performance of a server, without performing specific actions involving data transmission
        registry.antMatchers(HttpMethod.OPTIONS, "/**").denyAll();
        // Automatic login - cookie storage mode
        registry.and().rememberMe();
        // All other requests require authentication
        registry.anyRequest().authenticated();
        // Prevent iframe from causing cross-domain
        registry.and().headers().frameOptions().disable();

        // Custom Filter Authenticates User Name Password
        http.addFilterAt(adminAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

2. Custom User Password Check Filter

@Slf4j
@Component
public class AdminAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {

    /**
     * @param authenticationManager:             Authentication Manager
     * @param adminAuthenticationSuccessHandler: Authentication Successful Processing
     * @param adminAuthenticationFailureHandler: Authentication Failure Handling
     */
    public AdminAuthenticationProcessingFilter(CusAuthenticationManager authenticationManager, AdminAuthenticationSuccessHandler adminAuthenticationSuccessHandler, AdminAuthenticationFailureHandler adminAuthenticationFailureHandler) {
        super(new AntPathRequestMatcher("/login", "POST"));
        this.setAuthenticationManager(authenticationManager);
        this.setAuthenticationSuccessHandler(adminAuthenticationSuccessHandler);
        this.setAuthenticationFailureHandler(adminAuthenticationFailureHandler);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (request.getContentType() == null || !request.getContentType().contains(Constants.REQUEST_HEADERS_CONTENT_TYPE)) {
            throw new AuthenticationServiceException("Request header type is not supported: " + request.getContentType());
        }

        UsernamePasswordAuthenticationToken authRequest;
        try {
            MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(request);
            // Converting Front-end Transferred Data into jsonBean Data Format
            User user = JSONObject.parseObject(wrappedRequest.getBodyJsonStrByJson(wrappedRequest), User.class);
            authRequest = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), null);
            authRequest.setDetails(authenticationDetailsSource.buildDetails(wrappedRequest));
        } catch (Exception e) {
            throw new AuthenticationServiceException(e.getMessage());
        }
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

3. Custom Authentication Manager

@Component
public class CusAuthenticationManager implements AuthenticationManager {

    private final AdminAuthenticationProvider adminAuthenticationProvider;

    public CusAuthenticationManager(AdminAuthenticationProvider adminAuthenticationProvider) {
        this.adminAuthenticationProvider = adminAuthenticationProvider;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Authentication result = adminAuthenticationProvider.authenticate(authentication);
        if (Objects.nonNull(result)) {
            return result;
        }
        throw new ProviderNotFoundException("Authentication failed!");
    }
}

4. Custom Authentication Processing

PasswordUtils, a cryptographic verification tool class, can be viewed in the source code at the end of the article.

@Component
public class AdminAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    UserDetailsServiceImpl userDetailsService;
    @Autowired
    private UserMapper userMapper;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // Get the user name and password returned after input in the front-end form
        String userName = (String) authentication.getPrincipal();
        String password = (String) authentication.getCredentials();

        SecurityUser userInfo = (SecurityUser) userDetailsService.loadUserByUsername(userName);

        boolean isValid = PasswordUtils.isValidPassword(password, userInfo.getPassword(), userInfo.getCurrentUserInfo().getSalt());
        // Verify password
        if (!isValid) {
            throw new BadCredentialsException("Error password!");
        }

        // Processing logic in case of front-end and back-end separation.
        // Update the login token - then access other interfaces of the system to authenticate user rights directly through token.
        String token = PasswordUtils.encodePassword(System.currentTimeMillis() + userInfo.getCurrentUserInfo().getSalt(), userInfo.getCurrentUserInfo().getSalt());
        User user = userMapper.selectById(userInfo.getCurrentUserInfo().getId());
        user.setToken(token);
        userMapper.updateById(user);
        userInfo.getCurrentUserInfo().setToken(token);
        return new UsernamePasswordAuthenticationToken(userInfo, password, userInfo.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

Among them, a UserDetailsServiceImpl class is customized to implement the UserDetailsService class - > for authenticating user details. Implementing UserDetails Class with Customizing a SecurityUser Class - > Secure Authentication User Details

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    /***
     * Getting User Information Based on Account
     * @param username:
     * @return: org.springframework.security.core.userdetails.UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // Extract user information from the database
        List<User> userList = userMapper.selectList(new EntityWrapper<User>().eq("username", username));
        User user;
        // Determine whether the user exists or not
        if (!CollectionUtils.isEmpty(userList)){
            user = userList.get(0);
        } else {
            throw new UsernameNotFoundException("User name does not exist!");
        }
        // Return the UserDetails implementation class
        return new SecurityUser(user);
    }
}

Secure Authentication User Details

@Data
@Slf4j
public class SecurityUser implements UserDetails {
    /**
     * Current logged-in user
     */
    private transient User currentUserInfo;

    public SecurityUser() {
    }

    public SecurityUser(User user) {
        if (user != null) {
            this.currentUserInfo = user;
        }
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority("admin");
        authorities.add(authority);
        return authorities;
    }

    @Override
    public String getPassword() {
        return currentUserInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return currentUserInfo.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

5. Custom Authentication Success or Failure Processing

  1. Authentication SuccessHandler Class Rewriting onAuthentication Success Method Implemented by Authentication SuccessHandler Class
  2. Authentication Failure Handler Class Rewriting onAuthentication Failure Method Implemented by Authentication Failure Handler Class

In the case of front-end and back-end separation, both success and failure of trivia authentication return to json data format

After the certification is successful, only one token is returned to the front end, and other information can be processed according to the actual business of the individual.

@Component
public class AdminAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
        User user = new User();
        SecurityUser securityUser = ((SecurityUser) auth.getPrincipal());
        user.setToken(securityUser.getCurrentUserInfo().getToken());
        ResponseUtils.out(response, ApiResult.ok("Login successfully!", user));
    }
}

Authentication Failure Capture Exception Custom Error Information Returns to Front End

@Slf4j
@Component
public class AdminAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        ApiResult result;
        if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
            result = ApiResult.fail(e.getMessage());
        } else if (e instanceof LockedException) {
            result = ApiResult.fail("The account is locked. Please contact the administrator.!");
        } else if (e instanceof CredentialsExpiredException) {
            result = ApiResult.fail("Certificate expires, please contact administrator!");
        } else if (e instanceof AccountExpiredException) {
            result = ApiResult.fail("Account expires, please contact the administrator!");
        } else if (e instanceof DisabledException) {
            result = ApiResult.fail("Account disabled, please contact administrator!");
        } else {
            log.error("Logon failed:", e);
            result = ApiResult.fail("Login failed!");
        }
        ResponseUtils.out(response, result);
    }
}
Warm tips:

In the case of front-end and back-end integration, exception handling interface can be configured in Spring Security core configuration class and exception information can be obtained as follows

AuthenticationException e = (AuthenticationException) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
System.out.println(e.getMessage());

3. Front-end pages

Here are two simple html pages that simulate landing processing scenarios with front-end and back-end separation

1. Landing Page

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<h1>Spring Security</h1>
<form method="post" action="" onsubmit="return false">
    <div>
        //User name: <input type="text" name="username" id="username">
    </div>
    <div>
        //Password: <input type= "password" name= "password" id= "password">
    </div>
    <div>
<!--        <label><input type="checkbox" name="remember-me" id="remember-me"/>automatic logon</label>-->
        <button onclick="login()">Land</button>
    </div>
</form>
</body>
<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js" type="text/javascript"></script>
<script type="text/javascript">
    function login() {
        var username = document.getElementById("username").value;
        var password = document.getElementById("password").value;
        // var rememberMe = document.getElementById("remember-me").value;
        $.ajax({
            async: false,
            type: "POST",
            dataType: "json",
            url: '/login',
            contentType: "application/json",
            data: JSON.stringify({
                "username": username,
                "password": password
                // "remember-me": rememberMe
            }),
            success: function (result) {
                console.log(result)
                if (result.code == 200) {
                    alert("Landing successfully");
                    window.location.href = "../home.html";
                } else {
                    alert(result.message)
                }
            }
        });
    }
</script>
</html>
2, home page

home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>Hello, landing success</h3>
<button onclick="window.location.href='/logout'">Logout</button>
</body>
</html>

IV. Test Interface

@Slf4j
@RestController
public class IndexController {

    @GetMapping("/")
    public ModelAndView showHome() {
        return new ModelAndView("home.html");
    }

    @GetMapping("/index")
    public String index() {
        return "Hello World ~";
    }

    @GetMapping("/login")
    public ModelAndView login() {
        return new ModelAndView("login.html");
    }

    @GetMapping("/home")
    public String home() {
        String name = SecurityContextHolder.getContext().getAuthentication().getName();
        log.info("Landers:" + name);
        return "Hello~ " + name;
    }

    @GetMapping(value ="/admin")
    // Access path `admin'has `crud' permission
    @PreAuthorize("hasPermission('/admin','crud')")
    public String admin() {
        return "Hello~ Administrators";
    }

    @GetMapping("/test")
//    @PreAuthorize("hasPermission('/test','t')")
    public String test() {
        return "Hello~ Test permission access interface";
    }

    /**
     * Logon exception handling - in the case of front-end and back-end integration
     * @param request
     * @param response
     */
    @RequestMapping("/login/error")
    public void loginError(HttpServletRequest request, HttpServletResponse response) {
        AuthenticationException e = (AuthenticationException) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
        log.error(e.getMessage());
        ResponseUtils.out(response, ApiResult.fail(e.getMessage()));
    }
}

V. Testing Access Effectiveness

Database account: admin password: 123456

1. Enter the wrong username to indicate that the user does not exist

2. Input error password prompt password error

3. Enter the correct username and account number, prompt login success, and then jump to the home page.

After successful login, other interfaces can be accessed normally, but they will not be accessed if they are not logged in.

Warm Tip: The back-end has not been dealt with while not logged in or accessing an unauthorized interface. The relevant cases will be explained in the following privilege control case tutorial.

Six, summary

  1. Set up a custom user password checking filter (Admin Authentication Processing Filter) in the Spring Security core configuration class
  2. Configuration of CusAuthentication Manager, Admin Authentication Success Handler and Admin Authentication Failure Handler in a custom user password verification filter
  3. Configure custom authentication processing (Admin Authentication Provider) in a custom authentication manager
  4. Then it implements its own business logic in authentication processing.
Security related code structure:

The source code of this case

https://gitee.com/zhengqingya/java-workspace

Keywords: Programming Spring JSON Database JQuery

Added by CFennell on Mon, 14 Oct 2019 08:50:49 +0300