One, Spring Security framework
1. Introduction to the Framework
Official introduction: Spring Security is a powerful and highly customizable authentication and access control framework. It is the de facto standard for protecting Spring-based applications. Spring Security is a framework that focuses on providing authentication and authorization for Java applications. Like all Spring projects, the real power of Spring Security is that it can be easily extended to meet custom requirements.
Spring Security is a security framework that provides a declarative security access control solution for Spring-based enterprise application systems (in short, it controls access permissions). Application security includes user authentication (Authentication) and user authorization (Authorization) Two parts. The main core functions of Spring Security are authentication and authorization, and all architectures are also implemented based on these two core functions.
User authentication refers to verifying whether a user is a legal subject in the system, that is, whether the user can access the system. User authentication generally requires the user to provide a user name and password. The system completes the authentication process by verifying the user name and password.
User authorization refers to verifying whether a user has the authority to perform a certain operation. In a system, different users have different permissions. For example, for a file, some users can only read it, and some users can modify it. Generally speaking, the system assigns different roles to different users, and each role corresponds to a series of permissions.
feature:
- Comprehensive and scalable support for authentication and authorization
- Prevent attacks such as session fixation, clickjacking, and cross-site request forgery
- Servlet API integration
- Optional integration with Spring Web MVC
2. Framework Principle
The best way to protect Web resources is Filter, and the best way to protect method calls is AOP. Therefore, when we authenticate users and grant permissions, Spring Security uses various interceptors to control access to permissions to achieve security.
The main filters of the Spring Security framework (Filter):
The best way to protect Web resources is Filter, and the best way to protect method calls is AOP. Therefore, when we authenticate users and grant permissions, Spring Security uses various interceptors to control access to permissions to achieve security.
The main filters of the Spring Security framework (Filter):
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- LogoutFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- AnonymousAuthenticationFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
- UsernamePasswordAuthenticationFilter
- BasicAuthenticationFilter
The core components of the Spring Security framework:
- SecurityContextHolder: Provides access to SecurityContext
- SecurityContext: Holds the Authentication object and other information that may be needed
- AuthenticationManager which can contain multiple AuthenticationProviders
- The ProviderManager object is the implementation class of the AuthenticationManager interface
- AuthenticationProvider is mainly used for authentication operations. The class calls the authenticate() method to perform authentication operations.
- Authentication: Authentication subject in Spring Security mode
- GrantedAuthority: Application-level authorization for authentication topics, including the authorization information of the current user, usually represented by roles
- UserDetails: the necessary information to construct the Authentication object, which can be customized, and may need to be obtained by accessing the DB
- UserDetailsService: Construct UserDetails object through username, and obtain UserDetail object according to userName through loadUserByUsername (you can customize implementation based on your own business here, such as through database, xml, cache, etc.)
Two, SpringBoot integrates Spring Security
1. Project Environment
(1) JDK version: 1.8
(2)Spring Boot:2.1.2.RELEASE
(3)Spring Security 5.1.3
(4)IntelliJ IDEA 2016.3.4
2. Build database and table
The table creation statement is as follows, the password corresponding to the user has been encrypted with md5.
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`userName` varchar(255) DEFAULT NULL COMMENT '姓名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`roles` varchar(255) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户表';
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'ouyang', 'bbd126f4856c57f68d4e30264da6a4e6', 'ROLE_ADMIN,ROLE_USER');
INSERT INTO `user` VALUES ('2', 'admin', 'bbd126f4856c57f68d4e30264da6a4e6', 'ROLE_ADMIN');
INSERT INTO `user` VALUES ('3', 'user', 'bbd126f4856c57f68d4e30264da6a4e6', 'ROLE_USER');
3. Add dependencies and configure yml
Depend on pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.oycbest</groupId>
<artifactId>springsecuritytest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecuritytest</name>
<description>springsecurity 安全认证框架实战项目</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<!--Spring Boot热加载 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--Spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--JDBC-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
<!--spring-data-jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml placement:
server:
port: 8082
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/springsecurity?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8
username: ouyangcheng
password: 123456
driver-class-name: com.mysql.jdbc.Driver
druid:
initialSize: 1
minIdle: 1
maxActive: 50
maxWait: 6000
timeBetweenEvictionRunsMillis: 6000
minEvictableIdleTimeMillis: 30000
testWhileIdle: true
testOnBorrow: true
testOnReturn: true
validationQuery: SELECT 1 from dual
connectionProperties: config.decrypt=false
4. Create spring security configuration file
package com.oycbest.config;
import com.oycbest.domain.User;
import com.oycbest.service.PasswordEncoder;
import com.oycbest.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.annotation.Resource;
/**
* @Author: oyc
* @Date: 2019/1/29 13:45
* @Description:
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserService<User> userService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//允许基于HttpServletRequest使用限制访问
http.authorizeRequests()
//不需要身份认证
.antMatchers("/", "/home","/toLogin","/**/customer/**").permitAll()
.antMatchers("/js/**", "/css/**", "/images/**", "/fronts/**", "/doc/**", "/toLogin").permitAll()
.antMatchers("/user/**").hasAnyRole("USER")
//.hasIpAddress()//读取配置权限配置
.antMatchers("/**").access("hasRole('ADMIN')")
.anyRequest().authenticated()
//自定义登录界面
.and().formLogin().loginPage("/toLogin").loginProcessingUrl("/login").failureUrl("/toLogin?error").permitAll()
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.and().exceptionHandling().accessDeniedPage("/toLogin?deny")
.and().httpBasic()
.and().sessionManagement().invalidSessionUrl("/toLogin")
.and().csrf().disable();
}
}
5. Create UserService to implement UserDetialsServer class
This class mainly implements the loadUserByname method, and then we can inject our service, repository or mapper interface into this class, and then obtain the user according to the username inside the method, and then obtain the user's permissions. Query the database through the user name. If the user is not found, the login fails and stays on the original web page. If the user is found, the user's account password and all the permissions of the found user are encapsulated into the security user Returned in the class (the obtained form is org.springframework.security.core.userdetails.User@c41325a7: Username: ouyang; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN, ROLE_USER), then security will compare the password sent from the front desk with the password found to see if the login is successful, and the failure stays on the original web page. This step is solved by the framework itself, no need for us to judge, just Return to User.
I directly inject UserRepository here, and use UserRepository to directly obtain user information and permissions. If you have questions about UserRepository, please refer to the previous section of Spring Boot Introduction Series 8 (SpringBoot integrates SpringData JPA to operate Mysql database): https://blog.csdn.net/u014553029 /article/details/86662518
package com.oycbest.service;
import com.oycbest.domain.User;
import com.oycbest.repository.UserRepository;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: oyc
* @Date: 2019/1/29 14:19
* @Description: 用户服务类
*/
@Service
public class UserService<T extends User> implements UserDetailsService {
@Resource
private UserRepository<User> repository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
User user = repository.findByUserName(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
//用户权限
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (StringUtils.isNotBlank(user.getRoles())) {
String[] roles = user.getRoles().split(",");
for (String role : roles) {
if (StringUtils.isNotBlank(role)) {
authorities.add(new SimpleGrantedAuthority(role.trim()));
}
}
}
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), authorities);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
6. Other
The login.html, MD5Util and PasswordEncoder used, the code is as follows:
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}" style="color: red;">
Invalid username and password.
</div>
<div th:if="${param.logout}" style="color: red;">
You have been logged out.
</div>
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="submit"/> <input type="reset" value="reset"/></div>
</form>
<p>Click <a th:href="@{/home}">here</a> go to home page.</p>
</body>
</html>
MD5Util.java
package com.oycbest.util;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @Author: oyc
* @Date: 2018/12/3 11:11
* @Description: MD5加密工具
*/
public class MD5Util {
public static final int time = 5;
public static final String SALT = "springsecurity";
/**
* 密码加密方法
*
* @param password
* @return
*/
public static String encode(String password) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK).");
}
try {
for (int i = 0; i < time; i++) {
byte[] bytes = digest.digest((password + SALT).getBytes("UTF-8"));
password = String.format("%032x", new BigInteger(1, bytes));
}
return password;
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK).");
}
}
public static void main(String[] args) {
System.out.println(MD5Util.encode("123456"));
}
}
PasswordEncoder.java
package com.oycbest.service;
import com.oycbest.util.MD5Util;
/**
* @Author: oyc
* @Date: 2018/12/3 10:29
* @Description: 密码加密类
*/
public class PasswordEncoder implements org.springframework.security.crypto.password.PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return MD5Util.encode((String) rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {//user Details Service验证
return encodedPassword.equals(MD5Util.encode((String) rawPassword));
}
}
Three, test
First log in with user ouyang, because ouayng has all permissions, so you can access admin and user; when you log in with admin user, you can access admin normally, but when you access the user path, it will be blocked because the admin user does not have access The permissions of the user path.
UserService中的loadUserByUsername在认证过程获取数据如下:
Source address: https://github.com/oycyqr/SpringSecurity