First, the introduction of dependence:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
After the introduction of this dependency, your web application will have the following features:
- All requests require certification path
- No specific roles and permissions
- No login page, using HTTP Basic authentication
- Only one user name for the user
Configuration SpringSecurity
springsecurity configuration item, is preferably stored in a separate configuration classes:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
Configuring user authentication
First of all, to solve is user registration, storing user information. springsecurity offers four stores the user's way:
- Memory-based (production certainly does not use)
- Based on JDBC
- Based on LDAP
- User-defined (most common)
Using any of a manner to cover the need configure(AuthenticationManagerBuilder auth)
methods:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
}
1. Based on Memory
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123").authorities("ROLE_USER")
.and()
.withUser("lisi").password("456").authorities("ROLE_USER");
}
2. Based on JDBC
@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource);
}
JDBC-based way, you must have some specific exemplar, and field meet its query rules:
public static final String DEF_USERS_BY_USERNAME_QUERY =
"select username,password,enabled " +
"from users " +
"where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"select username,authority " +
"from authorities " +
"where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"select g.id, g.group_name, ga.authority " +
"from groups g, group_members gm, group_authorities ga " +
"where gm.username = ? " +
"and g.id = ga.group_id " +
"and g.id = gm.group_id";
Of course, you can modify these statements about:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery("select username, authority from UserAuthorities " +
"where username=?");
There is a problem, your database password encryption may be a way encrypted, and the user is transmitted in plain text, more time needs to be encrypted, springsecurity also provides a corresponding function:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery("select username, authority from UserAuthorities " +
"where username=?")
.passwordEncoder(new StandardPasswordEncoder("53cr3t");
passwordEncoder
The method of delivery is the PasswordEncoder
implementation of the interface, which provides a number of default to achieve, if not satisfied, you can implement this interface:
BCryptPasswordEncoder
- NoOpPasswordEncoder
- Pbkdf2PasswordEncoder
- Shcryptfssvordetrrchoder
StandardPasswordEncoder(SHA-256)
3. Based on LDAP
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("passcode")
.contextSource()
.root("dc=tacocloud,dc=com")
.ldif("classpath:users.ldif");
4. User-defined method (most commonly used)
First, you need a user entity class that implements UserDetails
the interface, the interface to achieve this is to provide more information to the frame, you can see it as Entity Framework classes used:
@Data
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private String fullname;
private String city;
private String phoneNumber;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
With entity class, you also need Service logic layer, springsecurity provides UserDetailsService
interfaces, see the name to know Italian, you just by loadUserByUsername
returning a UserDetails
target to become, whether it is file-based, database, or based on LDAP, the rest pay a comparative judgment framework completed:
@Service
public class UserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return null;
}
}
Finally, the application:
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder encoder() {
return new StandardPasswordEncoder("53cr3t");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
Configuration certification path
Know how to authenticate, but now there are several issues, such as user login page does not need certification, it can be used configure(HttpSecurity http)
to configure the authentication path:
@Override
protected void configure(HttpSecurity http) throws Exception {
}
You can use this method, the following functions:
- Before providing the interface service, the request must meet certain conditions determined
- Configuring the login page
- It allows the user to log out
- Cross-site request forgery protection
1. Protection Request
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").hasRole("ROLE_USER")
.antMatchers(“/”, "/**").permitAll();
}
Pay attention to the order, in addition to hasRole and permitAll there are other access authentication methods:
method | effect |
---|---|
access(String) | If given the results SpEL expression is true, allows access |
anonymous() | Allow access to anonymous users |
authenticated() | It allows users to access authenticated |
denyAll () | Unconditional Access Denied |
fullyAuthenticated() | If the user is fully authenticated, access is granted |
hasAnyAuthority (String ...) | If any user has given permission, allow access |
hasAnyRole (String ...) | If the user has any given role, it allows access |
hasAuthority(String) | If the user has given permission, allow access |
hasIpAddress(String) | If a request from a given IP address, access is granted |
hasRole(String) | If the user has given role is allowed access |
not() | Negative affect any other access method |
permitAll () | Allow unconditional access |
rememberMe() | Allows users to access authenticated by remember-me |
Most methods for the preparation of a specific embodiment, but access(String)
may be used SpEL into some special settings, but also a large part of the same method as above:
expression | effect |
---|---|
authentication | User authentication objects |
denyAll | Always evaluate to false |
hasAnyRole(list of roles) | If the user has any given role, that is true |
hasRole(role) | If the user has given role (true) |
hasIpAddress(IP address) | If the request from a given IP address that is true |
isAnonymous() | If the user is an anonymous user (true) |
isAuthenticated() | If the user is authenticated, that is true |
isFullyAuthenticated() | If the user is fully authenticated, for the true (not authenticated by remember-me) |
isRememberMe() | If the user is authenticated by remember-me, that is true |
permitAll | Always evaluate to true |
principal | The main target users |
Example:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('ROLE_USER') && " +
"T(java.util.Calendar).getInstance().get("+"T(java.util.Calendar).DAY_OF_WEEK) == " + "T(java.util.Calendar).TUESDAY")
.antMatchers(“/”, "/**").access("permitAll");
}
2. Configure the login page
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll")
.and()
.formLogin()
.loginPage("/login");
}
// 增加视图处理器
@Overridepublic void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
registry.addViewController("/login");
}
By default, I want to pass a username and password , but you can change:
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticate")
.usernameParameter("user")
.passwordParameter("pwd")
You can also modify the default login success page:
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/design")
3. Configure logout
.and()
.logout()
.logoutSuccessUrl("/")
4.csrf attack
springsecurity enabled by default to prevent csrf attack, you only need to add at the time of delivery:
<input type="hidden" name="_csrf" th:value="${_csrf.token}"/>
Of course, you can also turn off, but this is not recommended:
.and()
.csrf()
.disable()
You know who the user is
Only control user login and sometimes is not enough, you may also want to obtain user information has been registered in the rest of the program, there are several ways to do this:
The Principal Object injection control method
- The Authentication target injection control method
- Use SecurityContextHolder get the security context
Use @AuthenticationPrincipal annotation method
1. Principal Object injection control method
@PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus,Principal principal) {
...
User user = userRepository.findByUsername(principal.getName());
order.setUser(user);
...
}
2. Authentication target injection control method
@PostMappingpublic String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus, Authentication authentication) {
...
User user = (User) authentication.getPrincipal();
order.setUser(user);
...
}
3. Use SecurityContextHolder get the security context
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
4. @AuthenticationPrincipal annotation method
@PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus, @AuthenticationPrincipal User user) {
if (errors.hasErrors()) {
return "orderForm";
}
order.setUser(user);
orderRepo.save(order);
sessionStatus.setComplete();
return "redirect:/";
}