This article is a sub-chapter of the personal development framework for SpringBoot2.1. Please read the personal development framework for SpringBoot2.1
Back-end project address: personal application development framework for SpringBoot2.1
Front-end project address: yWH-vuE-admin
Reference:
- Spring Security is a beginner to advanced series of tutorials
- Detailed configuration of Spring Security
In the last article we have a preliminary understanding of Spring Security, we will mainly implement the database query user to authenticate whether the user has logged in.
Design of database tables
Reference:
- RBAC Authority Management this article is very detailed, but a little long ago, 12 years I was in senior one…
Before we do user login, we need to design the database table, RBAC (role-based Access Control), that is, users are associated with permissions through roles. Simply put, a user has several roles, and each role has several permissions. In this way, a user – role – permission authorization model is constructed. In this model, the relationship between users and roles, roles and permissions is generally many-to-many.
After the table structure is determined, we set up the entity, DAO, Service and THE XML corresponding to dao of the system user in the project. I will not show the code here. We can take the previous automatic generation of MybatisPlus and review the previous one by ourselves. After adding, you can now verify in the test class that there is no error before proceeding to the next step.
The security core classes
In the last note we know that one of the core classes of security WebSecurityConfigurerAdapter, we can create your own user in the configuration class, which request release, what certification request, in this note we want to achieve from the database query users, So we’ll implement the following core classes.
- Populated UserDetails interface
- UserDetailsService interface
Populated UserDetails interface
The UserDetails interface is an extensible user information interface provided by Security. It stores the information of some non-sensitive classes. This interface class is implemented in the Entity package under the Security module, which is actually an entity class.
package com.ywh.security.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
/** * CreateTime: 2019-01-24 16:10 * ClassName: SecurityUserDetails * Package: com.ywh.security.entity * Describe: * User details for Security * *@author YWH
*/
public class SecurityUserDetails implements UserDetails {
/** * User password */
private String password;
/** * User name */
private String username;
/** * User status. 1 indicates a valid user and 0 indicates an invalid user */
private Integer state;
/** * The user's permission, can put the user's role information set first, role represents the permission */
private Collection<? extends GrantedAuthority> authorties;
public SecurityUserDetails(String password, String username, Integer state, Collection<? extends GrantedAuthority> authorties) {
this.password = password;
this.username = username;
this.state = state;
this.authorties = authorties;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorties;
}
@Override
public String getPassword(a) {
return password;
}
@Override
public String getUsername(a) {
return username;
}
/** * indicates whether the user's account has expired. Expired accounts cannot be authenticated. *@returnTrue if the user's account is valid (that is, not expired), false if it is no longer valid (that is, expired) */
@JsonIgnore
@Override
public boolean isAccountNonExpired(a) {
return true;
}
/** * indicates whether the user is locked or unlocked. The locked user cannot be authenticated. *@returnTrue: unlocked; false: locked */
@JsonIgnore
@Override
public boolean isAccountNonLocked(a) {
return true;
}
/** * indicates whether the user's credentials (password) have expired. Expired credentials prevent authentication *@returnTrue if the user's credentials are valid (that is, not expired), false if they are no longer valid (that is, expired) */
@JsonIgnore
@Override
public boolean isCredentialsNonExpired(a) {
return true;
}
/** * indicates whether the user is enabled or disabled. Disabled users cannot be authenticated. *@returnThe true user is enabled. The false user is disabled. */
@JsonIgnore
@Override
public boolean isEnabled(a) {
return state == 1; }}Copy the code
UserDetailsService interface
This interface simply overwrites a method, loadUserByUsername, that queries the user’s details by user name and puts it in a subclass of our UserDetails implementation, stored in the SecurityContextHolder of Security. In the last article we used this to get user information.
This interface is implemented in the Service/IMPl of the Security module. The selectByUserName method is a normal DAO interface. The SQL statement is defined in the mapper. XML file, but I won’t post it because it is very simple
package com.ywh.security.service.impl;
/** * CreateTime: 2019-01-25 16:39 * ClassName: SecurityUserDetailsServiceImpl * Package: Com. Ywh. Security. Service. Impl * the Describe: this implementation class * * UserDetailService@PrimaryIndicates that the interface inherited from this class has multiple implementation classes, which are preferred when it is not known which to introduce@PrimaryAnnotated class *@author YWH
*/
@Primary
@Service
public class SecurityUserDetailsServiceImpl implements UserDetailsService {
// User query interface
private SysUserDao sysUserDao;
@Autowired
public SecurityUserDetailsServiceImpl(SysUserDao sysUserDao) {
this.sysUserDao = sysUserDao;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUserEntity sysUserEntity = sysUserDao.selectByUserName(username);
if(sysUserEntity ! =null) {// Stream A new feature in Java8, Stream provides a high-level abstraction of Java collection operations and expressions in an intuitive way similar to querying data from a database with SQL statements.
/ / reference http://www.runoob.com/java/java8-streams.html
List<SimpleGrantedAuthority> collect = sysUserEntity.getRoles().stream().map(SysRoleEntity::getSysRoleName)
.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return newSecurityUserDetails(sysUserEntity.getSysUserPassword(),sysUserEntity.getSysUserName(),sysUserEntity.getSysUserState(),co llect); }throw MyExceptionUtil.mxe(String.format("'%s'. This user does not exist", username)); }}Copy the code
Modify WebSecurityConfigurerAdapter implementation class
Of such in the last, we rewrite, we know that the user configuration is rewriting the configure (AuthenticationManagerBuilder auth) method, we are through the memory manager to create the user, this time we implemented UserDetailsService interface, We can use this to query the user and use it. Specific modifications are as follows:
package com.ywh.security.config;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
@Autowired
public SecurityConfigurer(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
/** * User information configuration *@paramAuth User information manager *@throwsException Exception information */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Before modifying
// auth
// .inMemoryAuthentication()
// .withUser("root")
// .password("root")
// .roles("user")
// .and()
// .passwordEncoder(CharEncoder.getINSTANCE());
// After modification
auth
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
/** * Password encryption *@returnReturn the encrypted password */
@Bean
public PasswordEncoder passwordEncoder(a) {
return newBCryptPasswordEncoder(); }}Copy the code
After restarting the project, we can see the effect as follows: the user ywh is in the database, and the password of the user is encrypted by the PasswordEncoder I manually called and then put into the database. The password is 123.
@Autowired
private PasswordEncoder passwordEncoder;
@Test
public void getOneTest(a){
System.out.println("password:" + passwordEncoder.encode("123"));
}
Copy the code
Custom login
See Spring Security for customizing user login authentication
After this step, we have finished querying the user login from the database, but the login page is the default page given by Security.
To use our custom page, Security continues to authenticate us, creating our custom login.html login page after creating a public directory in the Resource file in the core module
<html>
<head>
<title>Landing page</title>
</head>
<body>
<h2>Login form</h2>
<form action="/core/login" method="post">
<table>
<tr>
<td>User name:</td>
<td>
<input type="text" placeholder="Username" name="username" required="required" />
</td>
</tr>
<tr>
<td>Password:</td>
<td>
<input type="password" placeholder="Password" name="password" required="required" />
</td>
</tr>
<tr>
<td colspan="2">
<button type="submit">The login</button>
</td>
</tr>
</table>
</form>
</body>
</html>
Copy the code
Notice that the action property in the form has the same value as the loginProcessingUrl(“/login”) in the Security configuration class. I wrote /core/login because I configured context-path, Don’t write the /core prefix if you haven’t configured it
The access index. The HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>Logged in!!</body>
</html>
Copy the code
Modify the configure(HttpSecurity HttpSecurity) method of the Security configuration class
/** * Configure how to protect our requests through interceptors, what is allowed and what is not, and allow configuration for specific HTTP requests based on security considerations *@param httpSecurity http
* @throwsThe Exception Exception * /
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// Temporarily ban CSRC otherwise cannot be submitted
.csrf().disable()
If the second user logs in, the first user is kicked out and redirected to the login page
.sessionManagement().maximumSessions(1).expiredUrl("/login.html");
httpSecurity
// Start authentication
.authorizeRequests()
// Allow static files and login pages
.antMatchers("/static/**").permitAll()
.antMatchers("/auth/**").permitAll()
.antMatchers("/login.html").permitAll()
// Other requests require authentication login
.anyRequest().authenticated();
httpSecurity
// Form login
.formLogin()
// Set the login page to jump to
.loginPage("/login.html")
// .failureUrl("/auth/login? Error ") sets which page to jump to if an error occurs
// Security is the default login path authentication, if you want to use custom change can be used
.loginProcessingUrl("/login")
// If you access the login page directly, you will be redirected to this page after successful login. Otherwise, you will be redirected to the desired page
.defaultSuccessUrl("/index.html");
}
Copy the code
You can also enter an interface path in loginPage() to return to the loginPage. Through the above code we have implemented our own defined login page, security will intercept and authenticate for us, just create two pages and add two attributes.
Want to implement login successful custom logic, you can configure the successHandler () method, in order to realize the interface for AuthenticationSuccessHandler, rewrite the method for onAuthenticationSuccess ().
Custom exit
Want to implement custom exit function logic, you need to implement AuthenticationFailureHandler interface, rewrite the onAuthenticationFailure method. Or write anonymous inner classes in methods in the configuration.
Logout can be done by ‘/logout’, and an A tag can be added to the index.html interface.
<a href="/core/logout">exit</a>
Copy the code
Logout: context-path (‘ /core ‘); logout: context-path (‘ /core ‘); logout: context-path (‘ /core ‘)
- Disable the HttpSession
- Clean up remember passwords
- Clean up the SecurityContextHolder
- Redirect/login? logout
Of course we can also customize the exit by adding the following to configure(HttpSecurity HttpSecurity) method. Here I have implemented only the simplest operation
httpSecurity
/ / logout
.logout()
// logout using the default logout function of security. You can also customize the logout path
.logoutUrl("/logout")
// Which page to jump to after successful logout
.logoutSuccessUrl("/login.html")
.logoutSuccessHandler((request, response, authentication) -> {
// Logout success handler
System.out.println("logout success");
response.sendRedirect("/core/login.html");
})
.addLogoutHandler((request, response, authentication) ->{
// Logout handler
System.out.println("logout------");
})
/ / clean up the Session
.invalidateHttpSession(true);
Copy the code
As for security, we have realized the most basic functions. It can also do more things, such as remembering my password, third party login and so on.