1. Integrate advantages
SpringBoot can save us a lot of configuration files and project construction, and quickly import dependency packages, and SpringBoot is also a commonly used framework on the market, which can integrate most other frameworks on the market, and the use and development efficiency is very high.
2. Create a project
We create an empty project and introduce maven dependencies to build the springboot framework.
(1) Create an empty maven project and introduce dependencies.
<?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>
<groupId>com.mcs.security</groupId>
<artifactId>springboot-springsecurity</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.complier.source>1.8</maven.complier.source>
<maven.complier.target>1.8</maven.complier.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 数据库依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- jsp 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
</dependencies>
<build>
<!-- 改为你的项目名称 -->
<finalName>springboot-springsecurity</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<!--<configuration>
<path>/shiro</path>
<port>8080</port>
<uriEncoding>UTF-8</uriEncoding>
<url>http://localhost:8080/shiro</url>
<server>Tomcat7</server>
</configuration>-->
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>utf-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
(2) Spring container configuration
The springboot project will automatically scan all beans under the package where the startup class is located, and load them into the spring container.
Create application.properties under resource
我这里默认把后面用到的配置也贴了上来
server.port=8080
server.servlet.context-path=/springboot-springsecurity
spring.application.name=springboot-springsecurity
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 设置session超时时间
#server.servlet.session.timeout=10s
(3) SpringBoot startup class
The startup class should be created in the same directory as all subpackages
@SpringBootApplication
@Configuration
public class SecuritySpringBootApp {
public static void main(String[] args) {
SpringApplication.run(SecuritySpringBootApp.class, args);
}
}
(4) servlet Context configuration
is equivalent to the springMVC main configuration file when we configure the ssm framework.
The configuration here is not like the @EnableWebMvc and @ComponentScan annotations we needed last time. SpringBoot's automatic assembly mechanism will configure it for us.
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 默认url根路径跳转到mvc解析地址下的login
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login-view");
registry.addViewController("/login-view").setViewName("login");
}
}
It is much more convenient to configure the view resolver in the application.properties file than in the configuration file
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
(5) SpringSecurity configuration
saves @EnableWebSecurity configuration
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 配置模拟用户信息
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
return manager;
}
// 密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
// 配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/**").authenticated() // 拦截/r/**请求
.anyRequest().permitAll() // 其他请求正常方行
.and()
.formLogin().successForwardUrl("/login-success");
}
}
(6) Test
Add controller configuration
@RestController
public class loginController {
@RequestMapping(value = "/login-success", produces = {
"text/plain;charset=utf-8"})
public String loginSuccess() {
return "登录成功";
}
@RequestMapping(value = "/r/r1", produces = {
"text/plain;charset=utf-8"})
public String r1() {
return "访问资源r1";
}
@RequestMapping(value = "/r/r2", produces = {
"text/plain;charset=utf-8"})
public String r2() {
return "访问资源r2";
}
}
3. Custom Authentication UserDetailService
springSecurity authentication process
1. The username and password submitted by the user will be UsernamePasswordAuthentionFilter
obtained through the filter first, and encapsulated as Authentication.
2. Then submit the Authentication to the authentication manager for authentication AuthenticationMangere
. Authentication is a process of identifying the user's identity. After the authentication is successful, an Authentication object will be returned, which contains identity information. This identity information is an Object and will be forced into an object UserDetails
.
3. What is its certification process like? The authentication manager entrusts the authentication to AuthenticationProvider
the authentication, and the AuthenticationProvider stores a variety of authentication methods, such as SMS authentication, password authentication, etc., and the password authentication is to use the DaoAuthenticationProvider
authentication on the picture.
4. What does DaoAuthenticationProvider do internally? It is mentioned above that the last returned object is UserDetails. It can also be seen from the figure that this process is handed over to DaoAuthenticationProvider. UserDetailService is responsible for loading data from the database, querying data with its methods, UserDetailService
and loadUserByUsername
returning User Details.
5. When DaoAuthenticationProvider receives UserDetailService, it will perform password comparison, and finally encapsulate it into Authentication
an object and return it.
6. The last step in the figure is to save the logged-in user data.
After the above analysis, we probably know what UserDetailService is for. It simply obtains user data. Then we can implement this interface by customizing and rewrite the loadUserByUsername method to query users from the specified database.
We simulated user information in springSecurity before, we commented out the previous simulated data, we first implemented the interface, returned a user in loadUserByUsername (equivalent to already found), and then changed it to database query.
Comment out the simulated data in the springSecurity configuration file
// 配置模拟用户信息
/* @Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
return manager;
}*/
Implement the interface in the service package
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//登录账号
System.out.println("username="+username);
//根据账号去数据库查询...
//这里暂时使用静态数据
UserDetails userDetails =
User.withUsername(username).password("123").authorities("p1").build();
return userDetails;
}
}
Test
Insert a breakpoint at return userDeatils and run in Debug mode.
4. Connect to the database
The first step to connect to the database is to have a database (talking is equivalent to farting)
1. Creating a table
is very simple, just build it yourself.
2. Introduce dependencies. The pom.xml above has introduced all the dependencies required by the project.
3. Configure the database information in application.properties.
The above configuration file has been configured and will not be repeated.
4. Define the entity class
@Data
public class UserDto {
private String id;
private String username;
private String password;
private String fullname;
private String mobile;
}
5. Define the permanent layer Dao.
Because it is easy to query data, you can directly write methods in Dao. However, considering that Mybatis can be integrated in the future, I use a three-tier architecture to query. By the way, I also paste the entity classes and methods required by the authorization table. go in.
UserDao.java
@Repository
public interface UserDao {
// 根据账户查询
public UserDto getUserByUsername(String username);
// 根据id查询权限
public List<String> findPermissionByUserId(String userId);
}
UserDaoImpl.java
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public UserDto getUserByUsername(String username) {
String sql = "select id,username,password,fullname from t_user where username = ?";
List<UserDto> list = jdbcTemplate.query(sql, new Object[]{
username}, new BeanPropertyRowMapper<>(UserDto.class));
if (list == null) {
return null;
}
return list.get(0);
}
@Override
public List<String> findPermissionByUserId(String userId) {
// 根据用户id查询到其所拥有的权限信息
String sql = "select * from t_permission where id in \n" +
"(select permission_id from t_role_permission where role_id in \n" +
"(select role_id from t_user_role where user_id = ?))";
List<PermissionDto> list = jdbcTemplate.query(sql, new Object[]{
userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
// 从权限信息中剥离出权限字段
List<String> permissions = new ArrayList<String>(list.size());
list.iterator().forEachRemaining(c->permissions.add(c.getCode()));
return permissions;
}
}
UserDaoService.java
@Service
public interface UserDaoService {
// 根据账户查询
public UserDto getUserByUsername(String username);
// 根据id查询权限
public List<String> findPermissionByUserId(String userId);
}
UserDaoSerivceImpl.java
@Service
public class UserDaoServiceImpl implements UserDaoService {
@Autowired
UserDao userDao;
@Override
public UserDto getUserByUsername(String username) {
return userDao.getUserByUsername(username);
}
@Override
public List<String> findPermissionByUserId(String userId) {
return userDao.findPermissionByUserId(userId);
}
}
6. Modify the custom UserDetailService
to delete the original simulated data, and access the data directly from the database. Note that we have not established related tables in the database because of the permission information, so the permission temporarily uses static data, that is,.authorities("p1")
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
UserDaoService userDaoService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//根据账号去数据库查询
UserDto userByUsername = userDaoService.getUserByUsername(s);
if(userByUsername == null){
return null;
}
UserDetails userDetails = User.withUsername(userByUsername .getFullname()).password(userByUsername .getPassword()).authorities("p1").build();
return userDetails;
}
}
7. Test
Try to log in with data information, there are too many contents and no maps.
5. Password encryption PasswordEncoder
DaoAuthenticationiProvider performs password comparison through the matches method in the PasswordEncoder interface. SpringSecurity provides many built-in PasswordEncoders, which can be declared directly in its configuration file.
In the previous springSecurity configuration, the following password encryption method is used
// 密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
This is a string matching method for comparison without encryption, just a simple comparison of passwords.
Use BCrypePasswordEncoder
for password encryption
1. Generally, the password is submitted to the background during registration and then encrypted and stored in the database, but we do not have the registration function, so we add a test class to convert the stored password into BCrypePasswordEncoder format, and then manually save it to the database .
Create a test method in test
@RunWith(SpringRunner.class)
public class BcryTest {
@Test
public void test1() {
// 对原始密码进行加密
String password = BCrypt.hashpw("123", BCrypt.gensalt());
System.out.println(password);
}
}
Just save the above password to our data.
2. Configure the encryption method
and configure it in the security configuration file as follows
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
3. Test whether you can log in
6. Customize the authentication page
In the past, we used the login and exit interfaces that come with springSecurity. Next, we customize the login interface.
1. Create a login page
Created under the specified address configured by the view resolver, webapp is the same level directory as resource.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<form action="login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
2. Configure the address in webConfig.java,
which should have been pasted above
// 默认url根路径跳转到mvc解析地址下的login
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login-view");
registry.addViewController("/login-view").setViewName("login");
}
3. Configure security.
In order to prevent the occurrence of CSRF (Cross-siterequestforgery) cross-site request forgery, springsecurity restricts most methods except get. We can block CSRF.
// 配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //屏蔽CSRF控制,即springsecurity不再限制CSRF
.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/**").authenticated() // 拦截/r/**请求
.anyRequest().permitAll() // 其他请求正常方行
.and()
.formLogin() // 允许表单登录
.loginPage("/login-view") // 指定自己的登录页面,已经将 ‘/’ 路径跳到试图解析器配置路径下的 ‘login’
.loginProcessingUrl("/login") // 用户名密码提交的目的地址
.successForwardUrl("/login-success") // 指定登录成功后跳转的url
.permitAll() // .formLogin().permitAll()允许所有用户访问登录页面
.and()
.sessionManagement() // 会话控制
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login-view?logout");
}
7. Conversation function
The session function is to show who is accessing the resource when we access the resource. We define two resources in the controller, r1 and r2.
As mentioned in the analysis of the authentication process, the last step will store the logged-in user data in the SecurityContextHolder context, through which we can access user information, showing that the user has accessed the resource.
The modified controller
@RestController
public class loginController {
@RequestMapping(value = "/login-success", produces = {
"text/plain;charset=utf-8"})
public String loginSuccess() {
// 获取用户信息显示出来
return getUsername()+"登录成功";
}
@RequestMapping(value = "/r/r1", produces = {
"text/plain;charset=utf-8"})
public String r1() {
return getUsername()+"访问资源r1";
}
@RequestMapping(value = "/r/r2", produces = {
"text/plain;charset=utf-8"})
public String r2() {
return getUsername()+"访问资源r2";
}
// 获取用户姓名
private String getUsername() {
// Spring Security在认证完成后通过
// SecurityContextHolder.getContext().setAuthentication()方法将Authentication保存在上下文
// 所以我们可以通过访问上下文中的Authentication来获取用户信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
String username = null;
if(principal instanceof org.springframework.security.core.userdetails.UserDetails) {
username = ((UserDetails) principal).getUsername();
} else {
username = principal.toString();
}
return username;
}
}
Relevant user information will be displayed when accessing resources again.
Test
Log in to Zhangsan's account and access resource r1
8. Authorization
1. Create a table
The authorization function needs to access the user's permission information. The first step is to filter out which role the current user belongs to from the table, and then which permissions the role has.
角色表t_role
用户角色表t_user_role
权限表t_permission
角色权限表t_role_permission
2. Create a query permission method in the dao interface.
The relevant method code has been pasted in the fourth part when connecting data to create a three-tier architecture (too lazy)
3. Change UserDetailService to dynamically obtain permission
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
UserDto userByUsername = userDaoService.getUserByUsername(s);
if (userByUsername == null) {
return null;
}
// 查询权限字段信息
String userId = userByUsername.getId();
List<String> permissions = userDaoService.findPermissionByUserId(userId);
// 因为返回的UserDetails创建权限是变长度的String数组,转换一下
String[] per = new String[permissions.size()];
permissions.toArray(per);
// 模拟查询数据库
UserDetails p1 = User.withUsername(userByUsername.getUsername()).password(userByUsername.getPassword()).authorities(per).build();
return p1;
}
9. Integrate Mybatis
Haven't written yet, will definitely next time
The blog was written when I was learning the dark horse programmer SpringSecurity, if you don’t know it, you can leave a message in the comment area to communicate (manually bared teeth)