依赖
<?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 https://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.5.3</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.qinghe</groupId><artifactId>security-test</artifactId><version>0.0.1-SNAPSHOT</version><name>security-test</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><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.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></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><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
基本使用
代码
package com.qinghe.security.config;
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;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//所有请求 都需要认证
.authorizeRequests().anyRequest().authenticated()
.and()
//表单登陆
.formLogin()
.and()
.csrf().disable();
}
}
配置
server.port=7788
spring.security.user.name=zhangbo
spring.security.user.password=123
自定义登陆页面
代码
- SecurityConfig ```java package com.qinghe.security.config;
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;
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 所有请求 都需要认证
.authorizeRequests().anyRequest().authenticated()
.and()
// 表单登陆
.formLogin()
// 登陆页面
.loginPage("/login")
// 设置密码和用户名字段的别名
.passwordParameter("pwd")
.usernameParameter("username")
// 登陆失败页面
.failureUrl("/login?error")
// 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
// 前后端分离,一般会使用failureHandler,来返回json数据
.failureHandler((request, response, authenticationException) -> {
authenticationException.printStackTrace();
request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
})
.permitAll()
.and()
.csrf().disable();
}
}
- LoginController
```java
package com.qinghe.security.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String login(){
return "login";
}
}
配置
server.port=7788
spring.security.user.name=zhangbo
spring.security.user.password=123
spring.mvc.view.suffix=.html
- IndexController ```java package com.qinghe.security.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping;
@Controller public class IndexController {
@GetMapping("")
public String index(){
return "index";
}
}
<a name="b2BJK"></a>
## 页面
<a name="WqwTW"></a>
### login.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form method="post" action="/login">
用户名:<input name="username" type="text" />
<br/>
密码:<input name="pwd" type="text" />
<br/>
<button>登陆</button>
</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
ok
</body>
</html>
密码加密
在 自定义登陆页面 的基础上修改
package com.qinghe.security.config;
import org.springframework.context.annotation.Bean;
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.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private PasswordEncoder passwordEncoder;
/**
* http相关配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 所有请求 都需要认证
.authorizeRequests().anyRequest().authenticated()
.and()
// 表单登陆
.formLogin()
// 登陆页面
.loginPage("/login")
// 设置密码和用户名字段的别名
.passwordParameter("pwd")
.usernameParameter("username")
// 登陆失败页面
.failureUrl("/login?error")
// 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
// 前后端分离,一般会使用failureHandler,来返回json数据
.failureHandler((request, response, authenticationException) -> {
authenticationException.printStackTrace();
request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
})
.permitAll()
.and()
.csrf().disable();
}
/**
* 认证管理配置
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
// 内存配置用户
// 注意 roles 必须要,否则会报错,因为spring security 是基于rbac的
.inMemoryAuthentication()
.withUser("zhangbo").password(passwordEncoder.encode("123456")).roles("admin")
.and()
.withUser("test").password(passwordEncoder.encode("123456")).roles("user")
.and()
// 设置加密器
.passwordEncoder(passwordEncoder);
}
/**
* 加密器
* @return
*/
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
UserDetailsService
内存
package com.qinghe.security.config;
import org.springframework.context.annotation.Bean;
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.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import javax.annotation.Resource;
import java.util.Collections;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private UserDetailsService userDetailsService;
/**
* http相关配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 所有请求 都需要认证
.authorizeRequests().anyRequest().authenticated()
.and()
// 表单登陆
.formLogin()
// 登陆页面
.loginPage("/login")
// 设置密码和用户名字段的别名
.passwordParameter("pwd")
.usernameParameter("username")
// 登陆失败页面
.failureUrl("/login?error")
// 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
// 前后端分离,一般会使用failureHandler,来返回json数据
.failureHandler((request, response, authenticationException) -> {
authenticationException.printStackTrace();
request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
})
.permitAll()
.and()
.csrf().disable();
}
/**
* 用户详情服务-内存版
* @return
*/
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
User user = new User("zhangbo",passwordEncoder.encode("111111"),true,true,true,true, Collections.singleton(new SimpleGrantedAuthority("admin")));
manager.createUser(user);
manager.createUser(User.withUsername("test").password(passwordEncoder.encode("222222")).roles("user").build());
return manager;
}
/**
* 加密器
* @return
*/
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
JDBC
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置
server.port=7788
spring.mvc.view.suffix=.html
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3316/qinghe-auth?serverTimezone=GMT%2B8&characterEncoding=UTF-8&useSSL=false&autoReconnect=true
建表语句
create table users(username varchar(50) not null primary key,password varchar(500) not null,enabled boolean not null);
create table authorities (username varchar(50) not null,authority varchar(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
代码
package com.qinghe.security.config;
import org.springframework.context.annotation.Bean;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import javax.annotation.Resource;
import javax.sql.DataSource;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private UserDetailsService userDetailsService;
/**
* http相关配置
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 所有请求 都需要认证
.authorizeRequests().anyRequest().authenticated()
.and()
// 表单登陆
.formLogin()
// 登陆页面
.loginPage("/login")
// 设置密码和用户名字段的别名
.passwordParameter("pwd")
.usernameParameter("username")
// 登陆失败页面
.failureUrl("/login?error")
// 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
// 前后端分离,一般会使用failureHandler,来返回json数据
.failureHandler((request, response, authenticationException) -> {
authenticationException.printStackTrace();
request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
})
.permitAll()
.and()
.csrf().disable();
}
/**
* 认证配置
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
/**
* 用户详情服务-JDBC版本
* datasource 是自动注入的
*
* @return
*/
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
manager.setDataSource(dataSource);
// 以下代码,可以不要,只是为了方便创建用户
// 正式使用,在用户管理模块对用户进行操作
// 删除用户
//manager.deleteUser("zhangbo");
//manager.deleteUser("zhangyize");
if (!manager.userExists("zhangbo")) {
manager.createUser(User.withUsername("zhangbo").password(passwordEncoder.encode("123456")).roles("admin").build());
// 修改用户
manager.updateUser(User.withUsername("zhangbo").password(passwordEncoder.encode("123456")).roles("admin", "super_admin").build());
}
if (!manager.userExists("zhangyize")) {
// 创建用户
manager.createUser(User.withUsername("zhangyize").password(passwordEncoder.encode("111111")).roles("admin", "user").build());
}
return manager;
}
/**
* 加密器
*
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
注意:上面代码中,删除用户、创建用户、修改用户,在第一次启动的时候使用,后面可以不用。另外, UserDetailsService 服务也可以注入到别的业务中使用。
自定义
注意,这里我使用的是 MyBatis ,实际上就是查询数据,使用Redis、MongoDB、ElasticSearch、Oracle等都可以,只是查询下用户。
依赖
配置
代码
- SecurityConfig ```java package com.qinghe.security.config;
import com.qinghe.security.service.UserService; import org.springframework.context.annotation.Bean; 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.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private UserService userService;
/**
* http相关配置
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 所有请求 都需要认证
.authorizeRequests().anyRequest().authenticated()
.and()
// 表单登陆
.formLogin()
// 登陆页面
.loginPage("/login")
// 设置密码和用户名字段的别名
.passwordParameter("pwd")
.usernameParameter("username")
// 登陆失败页面
.failureUrl("/login?error")
// 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
// 前后端分离,一般会使用failureHandler,来返回json数据
.failureHandler((request, response, authenticationException) -> {
authenticationException.printStackTrace();
request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
})
.permitAll()
.and()
.csrf().disable();
}
/**
* 认证配置
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userService)
.passwordEncoder(passwordEncoder);
}
/**
* 加密器
*
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- UserService
`UserService` 只需要实现 `UserDetailsService` 就行了。
```java
package com.qinghe.security.service;
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;
@Service
public class UserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 在这里,可以使用任意数据库工具查询,我这里使用MyBatis
return null;
}
}
