Spring Security Basic Introduction
I won't give too much introduction to Spring Security here. For details, please refer to the official documentation.
I will only talk about the core functions of SpringSecurity:
Basic environment construction
Here we use SpringBoot as the basic framework of the project. I am using the maven method for package management, so here we first give the method of integrating Spring Security.
<dependencies> ... <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ...</dependencies>
Then establish a web layer request interface
@RestController@RequestMapping("/user")public class UserController { @GetMapping public String getUsers() { return "Hello Spring Security"; }}Next, you can run the project directly and call the interface to see the effect.
Calling through web page
We first make the interface call through the browser and directly access http://localhost:8080/user. If the interface can be accessed normally, then "Hello Spring Security" should be displayed.
But we cannot access it normally, and the authentication input box in the figure below appears
This is because in SpringBoot, the default Spring Security is effective. At this time, the interfaces are protected and we need to pass verification to access them normally. Spring Security provides a default user, the user name is user, and the password is automatically generated when the project is started.
When we check the project startup log, we will find the following log
Using default security password: 62ccf9ca-9fbe-4993-8566-8468cc33c28c
Of course, the password you see must be different from mine. We directly log in with the user and password in the startup log.
After logging in successfully, you jump to the page where the interface is called normally.
If you don't want to enable Spring Security from the beginning, you can configure the following in the configuration file:
# security Enable security.basic.enabled = false
The login box I just saw was provided by SpringSecurity, which is called httpBasicLogin. It is not what we want on our product. Our front-end generally performs user login verification through form submission, so we need to customize our own authentication logic.
Customize user authentication logic
Each system must have its own set of user systems, so we need to customize our own authentication logic and login interface.
Here we need to configure SpringSecurity accordingly
@Configurationpublic class BrowerSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // Define the login page to be transferred when the user is required to log in. .and() .authorizeRequests() // Define which URLs need to be protected and which do not need to be protected.anyRequest() // For any request, you can access .authenticated(); }}Next, configure the user authentication logic, because we have our own user system.
@Componentpublic class MyUserDetailsService implements UserDetailsService { private Logger logger = LoggerFactory.getLogger(getClass()); @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { logger.info("User's username: {}", username); // TODO According to the username, find the corresponding password, and encapsulate the user information and return. Parameters are: user name, password, user permissions User user = new User(userame, "123456", AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); return user; }} We did not perform too much verification here. The username can be filled in at will, but the password must be "123456" so that we can log in successfully.
At the same time, we can see that the third parameter of the User object here represents the permissions of the current user, and we set it to "admin".
Run the program to test it, and you will find that the login interface has changed.
This is because we configured http.formLogin() in the configuration file
Let’s just fill in a User here, and then fill in a wrong (non-123456) version. This will prompt a verification error:
At the same time, on the console, the user you just filled in when you log in will also be printed out.
Now let’s try logging in with the correct password. You can find that it will pass the verification and jump to the correct interface call page.
UserDetails
Just now, when we were writing MyUserDetailsService , we implemented a method and returned a UserDetails . This UserDetails is an object that encapsulates user information, which contains seven methods
public interface UserDetails extends Serializable { // Encapsulated permission information Collection<? extends GrantedAuthority> getAuthorities(); // Password information String getPassword(); // Login username String getUsername(); // Whether the account expires boolean isAccountNonExpired(); // Whether the account is frozen boolean isAccountNonLocked(); // Whether the account password expires, generally some systems with high password requirements will use it. Compared with the user is required to reset the password every once in a while boolean isCredentialsNonExpired(); // Is the account available? boolean isEnabled();}When we return the UserDetails implementation class User, we can set the corresponding parameters through the User constructor method
Password encryption and decryption
There is a PasswordEncoder interface in SpringSecurity
public interface PasswordEncoder { // Encrypt the password String encode(CharSequence var1); // Determine the password to match boolean matches(CharSequence var1, String var2);} We just need to implement this interface ourselves and configure it in the configuration file.
Here I'll temporarily test it with an implementation class provided by default
// BrowerSecurityConfig @Beanpublic PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }Encryption use:
@Componentpublic class MyUserDetailsService implements UserDetailsService { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { logger.info("User's username: {}", username); String password = passwordEncoder.encode("123456"); logger.info("password: {}", password); // The parameters are: user name, password, user permissions User user = new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); return user; }}Here we simply encrypt 123456. We can conduct tests and find that the password printed each time is different. This is the role of the configured BCryptPasswordEncoder.
Personalized user authentication logic
Custom login page
In previous tests, the default login interface has always been used. I believe that each product has its own login interface design, so we will learn about how to customize the login page in this section.
Let's write a simple login page first
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Login Page</title></head><body> <h2>Custom Login Page</h2> <form action="/user/login" method="post"> <table> <tr> <td> <td>Username:</td> <td><input type="text" name="username"></td> </tr> <tr> <td>Password:</td> <td><input type="password" name="password"></td> </tr> <tr> <td colspan="2"><button type="submit">Login</button></td> </tr> </table> </form></body></html>
After completing the login page, you need to configure it for SpringSecurity
// BrowerSecurityConfig.java@Overrideprotected void configure(HttpSecurity http) throws Exception { http.formLogin() // Define the login page to be transferred to when the user needs to log in. .loginPage("/login.html") // Set the login page.loginProcessingUrl("/user/login") // Custom login interface.and() .authorizeRequests() // Define which URLs need to be protected and which do not need to be protected.antMatchers("/login.html").permitAll() // Set everyone to access the login page.anyRequest() // For any request, you can access the .authenticated() .and() .csrf().disable(); // Turn off csrf protection}In this way, whenever we access the protected interface, we will be transferred to the login.html page
Handle different types of requests
Because now, the front and back ends are generally separated, the back end provides interfaces for front end to call and returns JSON format data to the front end. As before, the protected interface was called and the page jump was directly redirected. It is acceptable on the web side, but it is not possible on the App side, so we need to do further processing.
Here is a simple idea sorting out
First, write a custom controller and jump to it when identity authentication is required.
@RestControllerpublic class BrowserSecurityController { private Logger logger = LoggerFactory.getLogger(getClass()); // Caching and recovery of original request information private RequestCache requestCache = new HttpSessionRequestCache(); // Used to redirect private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); /** * When identity authentication is required, jump over* @param request * @param response * @return */ @RequestMapping("/authentication/require") @ResponseStatus(code = HttpStatus.UNAUTHORIZED) public BaseResponse requiresAuthenication(HttpServletRequest request, HttpServletResponse response) throws IOException { SavedRequest savedRequest = requestCache.getRequest(request, response); if (savedRequest != null) { String targetUrl = savedRequest.getRedirectUrl(); logger.info("The request to trigger a jump is:" + targetUrl); if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) { redirectStrategy.sendRedirect(request, response, "/login.html"); } } return new BaseResponse("The accessed service requires identity authentication, please guide the user to the login page"); }}Of course, the configuration file needs to be modified accordingly, so I won’t post the code here. It is to open the interface.
Extensions:
Here we write the interface that is accessed from a web page, then jump to the "/login.html" page. In fact, we can expand it and configure the jump address to the configuration file, which will be more convenient.
Custom processing login successful/failed
In previous tests, the page redirection was performed after successful login.
In the case of separation of front and back ends, if we log in successfully, we may need to return the user's personal information to the front end instead of jumping directly. The same is true for failed login.
This involves two interfaces in Spring Security AuthenticationSuccessHandler and AuthenticationFailureHandler . We can implement this interface and configure it accordingly. Of course, the framework has a default implementation class. We can inherit this implementation class and customize our business
@Component("myAuthenctiationSuccessHandler")public class MyAuthenctiationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { logger.info("Login successful"); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(authentication)); }} Here we return a JSON string through response.
The third parameter in this method, Authentication , contains the login user information (UserDetails), Session information, login information, etc.
@Component("myAuthenctiationFailureHandler")public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { logger.info("Login failed"); response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(new BaseResponse(exception.getMessage()))); }} The third parameter in this method AuthenticationException , includes information about login failure.
Similarly, it is still necessary to configure it in the configuration file. I will not post all the code here, only the corresponding statements are posted.
.successHandler(myAuthenticationSuccessHandler) // Custom login successfully handles.failureHandler(myAuthenticationFailureHandler) // Custom login failure handles
Code
You can click me to view the complete code
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.