SpringBoot+Vue SpringSecurity Login and authorization
Tools: IDEa2018, SpringBoot 2.1.4, SpringSecurity 5.1.5
Introduction to the
SpringSecurity is a security framework under Spring. Similar to Shiro, It is generally used for user Authentication and user Authorization. It is often integrated with SpringBoot.
Development steps
For ease of understanding, the next section uses the front and back end separation again and introduces database user and role information
Test the login
1 Importing Dependencies
(pom. XML)
<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>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Copy the code
2 Write test methods
(controller. UserController)
@Controller
public class UserController {
@GetMapping("/hello")
@ResponseBody
public String hello(a) {
return "hello controller"; }}Copy the code
3 test
Start the project, the browser access: localhost: 8080 / hello, address bar automatically jump to http://localhost:8080/login, enter the default landing page, verify login
Username defaults to user,Password is generated randomly (UUID), check console.
Spring Security intercepts URL access by default and provides an authenticated login page
Enter the password, my current is C1068CDB-18F3-48F4-B838-7698218D14C4. Login successful
The user name and password can be changed directly in the configuration file, for example
(application. The properties)
spring.security.user.name=admin
spring.security.user.password=123
Copy the code
Cut to the source
1> User parameters
Refer to the source code for static inner classes. As you can see, the default user password is actually a UUID.
(SpringSecurity — securityProperties.java)
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {...// Default user
private User user = newUser(); .public static class User {
// Default user name
private String name = "user";
// Default user name Default password, randomly generated
private String password = UUID.randomUUID().toString();
// The role of the default username
private List<String> roles = new ArrayList<>();
// Whether to generate a password
private boolean passwordGenerated = true; . }}Copy the code
2> Verify the user name and password
-
After the security dependency is imported, the default access path will pass through the filter and access its no-parameter construction to create a new post login request with the path /login.
-
The default login page is displayed
-
Get the username and password from the login form through the HttpServletRequest object
-
Create a token object for the username and password
-
Process login form information
(SpringSecurity – UsernamePasswordAuthenticationFilter. Java)
// @since spring security 3.0
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
// constructor to create matchers for post and HTTP methods in a case-insensitive manner.
public UsernamePasswordAuthenticationFilter(a) {
super(new AntPathRequestMatcher("/login"."POST"));
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if(postOnly && ! request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// Get the user name and password from the request path
String username = obtainUsername(request);
String password = obtainPassword(request);
// Null judgment
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
// Remove Spaces between the beginning and end of the user name
username = username.trim();
// Generate a token for username and password authentication
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Sets the authentication request information
setDetails(request, authRequest);
// Return a fully authenticated object, including credentials
return this.getAuthenticationManager().authenticate(authRequest); }...protected String obtainUsername(HttpServletRequest request) {
returnrequest.getParameter(usernameParameter); }}Copy the code
Custom login interface
(For ease of explanation, database information validation is not introduced)
1 Implementation interface
Implement the UserDetailsService interface and rewrite the method.
(service. MyUserDetailsSerice)
/** * Custom login interface (core interface, load user specific data.) * /
@Component
public class MyUserDetailsSerice implements UserDetailsService {
// Log returns the logger corresponding to the class passed as an argument
private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);
/** * Verify that user * is located based on the user name@paramUsername Indicates the name of the user whose data is required. *@returnCore user information, a fully populated user record *@throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("Login, username: {}", username);
return new User(username, "123", AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); }}Copy the code
2 Configure login blocking
Inheritance WebSecurityConfigurerAdapter configuration class, rewrite the configuration method
You can view springBoot on the official website or view the comments of the EnableWebSecurity interface
(config. MySecurityConfig)
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
// Basic configuration
http.httpBasic()
.and()
// Identity authentication
.authorizeRequests()
// All requests
.anyRequest()
// Identity authentication
.authenticated();
}
Copy the code
The returned User implements the UserDetail interface, as described in the source code
3 test
Start the project, clear the browser cache, access Hello, jump to the default login page, verify the password. For login, the user name and password must be any, and the password must be 123 (configured in MyUserDetailsSerice).
Logon failed, console prints, no target for ID “null” PasswordEncoder
4 Add the cipher encoder component
Inherit the PassawordEncoder interface
/** * The implementation class of the service interface used to encode the password. * /
@Component
public class MyPasswordEncoder implements PasswordEncoder {
/** * encode the original password. In general, good encoding algorithms apply sha-1 or larger hashes combined with 8 bytes or larger randomly generated salt. *@paramRawPassword password, a readable sequence of character values *@return* /
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
/** * Verify that the encoded password obtained from the store matches the original password submitted. Return true if the passwords match; If there is no match, return false. The stored password itself is never decoded. *@paramRawPassword Specifies the preset authentication password. The original password to encode and match *@paramEncodedPassword Specifies the password entered in the form. Compared to encoded ciphers from storage *@return* /
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
returnencodedPassword.equals(rawPassword.toString()); }}Copy the code
4 test
Restart the project, clear the browser cache, and access Hello.
Cut to the source
1 about WebSecurityConfigurerAdapter interface EnableWebSecurity for reference
(SpringSecurity — EnableWebSecurity)
/**
* Add this annotation to an {@code @Configuration} class to have the Spring Security * ............. * & # 064; Override * protected void configure(HttpSecurity http) throws Exception { * http.authorizeRequests().antMatchers(" /public/**") .permitAll().anyRequest() * .hasRole(" USER") .and() * // More configuration... *.formlogin () // make sure the base formLogin * // set the license *.permitall () for all urls associated with the formLogin; *} * *................... *@since3.2 * /.@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
// Disable the debug mode by default
boolean debug(a) default false;
}
Copy the code
Information about the default User encapsulated in 2 Security (SpringSecurity — user.java)
//
public class User implements UserDetails.CredentialsContainer{...private String password;
private final String username;
// Set of user permissions
private final Set<GrantedAuthority> authorities;
// The account has not expired
private final boolean accountNonExpired;
// The account is not locked
private final boolean accountNonLocked;
// The credentials have not expired
private final boolean credentialsNonExpired;
// The user is available
private final booleanenabled; . }Copy the code
Password encryption
1 Inject the cipher encoder object
Inheritance WebSecurityConfigurerAdapter configuration class
Inject a BCryptPasswordEncoder object directly into MySecurity. It implements the PasswordEncoder interface and overwrites the encode and matches methods
(config.MySecurityConfig.java)
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/** * implement cipher encoder using BCrypt strong hash function. The client can optionally provide "strength" (that is, the number of logging rounds in BCrypt) and SecureRandom instances. * The greater the strength parameter, the more work (exponentially) you need to do to hash the password. The default value is 10. *@return* /
@Bean
public PasswordEncoder passwordEncoder(a) {
return newBCryptPasswordEncoder(); }... }Copy the code
2 improve the service layer
Perfect MyUserDetailsSerice
(service. MyUserDetailsSerice. Java)
@Component
public class MyUserDetailsSerice implements UserDetailsService {...@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password = passwordEncoder.encode("123");
logger.info("Login, username: {}, password: {}", username,password);
return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); }}Copy the code
3 test
Comment out the @Component annotation of the MyPasswordEncoder so that it loses its container component identity
In debug mode, start the project and access Hello.
Debug displays the conversion of the original password 123 to $2aYGYb9i0ZjnTHPlOk/NQb/efrPNOaJq8hJYtdXf8VcdQUi8T8S3Iim
Console Logs
Cut to the source
As you can see, we’re automatically injecting the BCryptPasswordEncoder object and calling the encode method
(SpringSecurity — BCryptPasswordEncoder)
/ / the constructor
public BCryptPasswordEncoder(a) {
this(-1);
}
public BCryptPasswordEncoder(int strength) {... }public BCryptPasswordEncoder(int strength, SecureRandom random) {... }...public String encode(CharSequence rawPassword) {
/ / salt value
String salt;
// Check whether the constructor has parameters
if (strength > 0) {
if(random ! =null) {
// Salt generated by random and Strength
salt = BCrypt.gensalt(strength, random);
}
else {
// Salt generated by strengthsalt = BCrypt.gensalt(strength); }}// No arguments
else {
/ / call gensalt (GENSALT_DEFAULT_LOG2_ROUNDS); Randomly generated salt
// GENSALT_DEFAULT_LOG2_ROUNDS = 10
salt = BCrypt.gensalt();
}
// Use OpenBSD bcrypt to hash the password with the original password and salt value respectively
return BCrypt.hashpw(rawPassword.toString(), salt);
}
Copy the code
-
Here the BCryptPasswordEncoder uses no arguments, uses the default salt value, loops 10 times, and generates the hashed password.
-
Spring Security generates a random salt when encrypting the password, and the final encrypted password = password + random salt.
-
Notice the AuthorityUtils method here, and the parameter contains role information. In actual services, ROLE_** is used to specify the role field of a user and grant corresponding permissions after login
/** * Create an array of GrantedAuthority objects (for example, "ROLE_A, ROLE_B, ROLE_C") from a comma-separated string@paramAuthorityString A comma-separated string *@returnThrough the tag string create permissions/AuthorityUtils.com maSeparatedStringToAuthorityList (" admin ")Copy the code
Custom login request
Do not use the default login interface provided by springsecurity
1 Customize the front-end login page
(the template. The login. HTML)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>The login</title>
</head>
<body>
<h2>Welcome to login</h2>
<form action="/auth/login" method="post">
<input name="username" type="text" placeholder="Please enter user name.."><br/>
<input name="password" type="password" placeholder="Please enter your password..."><br/>
<input type="submit" value="Login">
</form>
</body>
</html>
Copy the code
2 Customize the home page
(template.index.html)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>The MAIN page</title>
</head>
<body>
<h1>Welcome to the home page</h1>
</body>
</html>
Copy the code
3 Add a forward path to the controller class
@Controller
public class UserController {
// Login test.// Login page, go to the /templates/login. HTML page
@GetMapping("/login")
public String login(a) {
return "login";
}
Go to the /templates/index.html page
@GetMapping("/index")
public String index(a) {
return "index"; }}Copy the code
4 Modify the interception configuration
Modify the configure method in MySecurityConfig
(config.MySecurityConfig.java)
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {...@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
// Form authentication
.formLogin()
/ / login page
.loginPage("/login")
// Login form submission address
.loginProcessingUrl("/auth/login")
.and()
// Authentication request
.authorizeRequests()
// The URL path matches
.antMatchers("/login").permitAll()
// Any request
.anyRequest()
// Identity authentication.authenticated(); }}Copy the code
LoginProcessingUrl (“/ Auth /login”) defines the form submission address, but there is no request path in UserController, SpringSecutity intercepts all requests by default, Redirect URL 302 to the /login default login page and use the default user name and password to login.
Custom login request status
Method 1: Inherit the interface implementation
1 User-defined login success class
(handler.MyAuthenticationSuccessHandler.java)
/** * inherits the interface for handling successful user authentication policies */
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);
// Provides the ability to read and write JSON, interact with basic POJO classes, interact with generic JSON tree models, and perform transformations.
@Autowired
private ObjectMapper objectMapper;
// Called when the user has successfully authenticated.
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("Login successful");
response.setContentType("application/json; charset=utf-8");
// writeValueAsString: Serializes a Java object into a stringresponse.getWriter().write(objectMapper.writeValueAsString(authentication)); }}Copy the code
2 Customize login failure classes
(handler.MyAuthenticationFailureHandler.java)
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
logger.info("Login failed");
// HTTP status, 200, success
response.setStatus(HttpStatus.OK.value());
response.setContentType("application/json; charset=utf-8"); response.getWriter().write(objectMapper.writeValueAsString(exception)); }}Copy the code
Method 2: Modify the configuration method of MySecurityConfig
1 Add methods for handling successful login and failed login
(config.MySecurityConfig.java)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/auth/login")
// Log in to the successful handler
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
ObjectMapper om = newObjectMapper(); String successMsg = om.writeValueAsString(om.writeValueAsString(authentication)); writer.write(successMsg); writer.flush(); writer.close(); }})// Failed to log in processor
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json; charset=utf-8");
PrintWriter writer = resp.getWriter();
writer.write(new ObjectMapper().writeValueAsString(e));
writer.flush();
writer.close();
}
})
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest()
.authenticated();
}
Copy the code
Get the current user information
(controller. UserController. Java)
@Controller
public class UserController {...// Current user information
@GetMapping("/info")
@ResponseBody
public Object getCurrentUser(Authentication authentication) {
returnauthentication; }}Copy the code
test
Start project, access /info, login successful, check F12