spring security 两个作用:
-
你是谁:who are you?
专业一点叫: Authentication(认证):
a. 内存认证
b. jdbc认证
c. UserDetailsService认证
d. ldap 认证
下面是源码中的四个认证方法
-
你能干什么:what are you allwoed to do?
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>
<groupId>wx0725.top</groupId>
<artifactId>guojihua</artifactId>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot提供的配置处理器依赖,代码提示 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- web 开发场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-devtools</artifactId>
</dependency>
<!-- 静态模板依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- IO 操作-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- 数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 阿里巴巴 适配的druid数据源启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- Spring Data JPA启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- redis 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JDBC 连接数据库的依赖启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
<!-- maven 打包工具等插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.3.RELEASE</version>
</plugin>
</plugins>
</build>
</project>
1. 内存认证
写一个配置类,自定义内存认证。
package wx0725.top.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
/**
* @author WEN
* @version 1.0
* @description: Wen Xuan
* @date 2021/5/7 下午 19:42
* @link http://wx0725.top
*/
@EnableWebSecurity
// 上面一个注解中包含下面三个注解
//@Import
//@EnableGlobalAuthentication
//@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// 选择定义密码加密算法
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 内存认证使用这个密码加密
authenticationManagerBuilder.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder)
// 注册一个身份,并且密码需要加密
.withUser("文轩").password(bCryptPasswordEncoder.encode("0725")).roles("common")
.and()
.withUser("wenxuan").password(bCryptPasswordEncoder.encode("wenxuan")).roles("vip");
}
}
新建一个index.html
<html>
<head>
<meta charset = "UTF-8">
<meta name = "viewport"
content = "width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv = "X-UA-Compatible" content = "ie=edge">
<title>首页</title>
</head>
<body>
<h1>hello world!</h1>
</body>
</html>
运行项目
输入:127.0.0.1:8082 也就是直接访问 spring boot 项目地址
此时会自动跳珠到login
可以看到login页面是从127.0.0.1:8082重定向过去的
2. JDBC 认证
需要新建一个安全检测的表格:
客户表:
身份表:
客户对应的身份表
在内存认证的配置类中如下代码:
package wx0725.top.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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 javax.sql.DataSource;
/**
* @author WEN
* @version 1.0
* @description: Wen Xuan
* @date 2021/5/7 下午 19:42
* @link http://wx0725.top
*/
@EnableWebSecurity
// 上面一个注解中包含下面三个注解
//@Import
//@EnableGlobalAuthentication
//@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// 选择定义密码加密算法
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 内存认证使用这个密码加密
authenticationManagerBuilder.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder)
// 注册一个身份,并且密码需要加密
.withUser("user").password(bCryptPasswordEncoder.encode("user")).roles("common")
.and()
.withUser("admin").password(bCryptPasswordEncoder.encode("admin")).roles("common");
// 使用JDBC认证
// 查询客户
String usql = "select username,password,valid from t_customer where username=?";
// 查询客户对应的身份
String asql = "select c.username, a.authority from t_customer c,t_authority a,t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username=?";
authenticationManagerBuilder.jdbcAuthentication().passwordEncoder(bCryptPasswordEncoder)
.dataSource(dataSource)
.usersByUsernameQuery(usql)
.authoritiesByUsernameQuery(asql);
}
// 允许忽略静态资源的安全访问
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/fonts/**");
}
}
登录成功之后会跳转到首页
这里其实跳到首页是因为第一次输入的时候,输入的是127.0.0.1:8082
如果你输入的是其他的页面,在登录成功之后会跳转到那个页面,并非跳转到首页。
3.UserDetailsService
这一部分代码不少:
其主要实现的是减少数据库压力,通过用户缓存信息,实现验证登录,在登录的时候只查询一次数据库,将其保存在缓存中,下次查询就直接读取缓存。
- 新建三个实体类
分别对应上面的三个表格,但是这里没有用到用户权限id对应的实体类,只在查询的时候用到表格了,所以该表没写对应的实体类
- Customer.java
- 保存用户对应的权限
package wx0725.top.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
/**
* @author WEN
* @version 1.0
* @description: Wen Xuan
* @date 2021/5/8 下午 13:58
* @link http://wx0725.top
*
* 用户信息
*/
@Entity(name = "t_customer")
public class Customer implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
private Integer valid;
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", valid=" + valid +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getValid() {
return valid;
}
public void setValid(Integer valid) {
this.valid = valid;
}
}
- Authority.java
- 保存用户的基本信息,这里是我自己设置的,没有对应数据库字段
package wx0725.top.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
/**
* @author WEN
* @version 1.0
* @description: Wen Xuan
* @date 2021/5/8 下午 14:13
* @link http://wx0725.top
* <p>
* 用户权限
*/
@Entity(name = "t_authority")
public class Authority implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String authority;
@Override
public String toString() {
return "Authority{" +
"id=" + id +
", authority='" + authority + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
}
- 还需对应写两个接口:
- CustomerRepository.java
- 通过用户名查询用户信息
package wx0725.top.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import wx0725.top.domain.Customer;
/**
* @author WEN
* @version 1.0
* @description: Wen Xuan
* @date 2021/5/8 下午 13:56
* @link http://wx0725.top
* <p>
* 用户信息查询接口
*/
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
// 可以直接使用JPA生成的实现
// @Query(value = "select c.* from t_customer c where c.username=?1", nativeQuery = true)
Customer findByUsername(String username);
}
- AuthorityRepository.java
- 通过用户名查询用户对应的权限
- 需要注意的是查询时需要些 a.* 不然会报错,懵逼,这里没查到
package wx0725.top.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import wx0725.top.domain.Authority;
import java.util.List;
/**
* @author WEN
* @version 1.0
* @description: Wen Xuan
* @date 2021/5/8 下午 13:57
* @link http://wx0725.top
*/
public interface AuthorityRepository extends JpaRepository<Authority, Integer> {
// a.*
@Query(value = "select a.* from t_customer c,t_authority a,t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username=?1", nativeQuery = true)
List<Authority> findAuthoritiesByUsername(String s);
}
- 接下来就是具体的实现操作,也就是业务层
对接实体层与控制层
- CustomerService.java
- 用来获取用户信息与权限,方便控制层调用
package wx0725.top.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import wx0725.top.domain.Authority;
import wx0725.top.domain.Customer;
import wx0725.top.repository.AuthorityRepository;
import wx0725.top.repository.CustomerRepository;
import java.util.List;
/**
* @author WEN
* @version 1.0
* @description: Wen Xuan
* @date 2021/5/8 下午 13:54
* @link http://wx0725.top
* <p>
* 业务处理
* 用来通过用户名查询用户信息、权限
*/
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
@Autowired
private AuthorityRepository authorityRepository;
@Autowired
private RedisTemplate redisTemplate;
private String cacheName = "customer::";
// 用户名查询用户
public Customer getCustomer(String username) {
Customer customer = null;
Object o = redisTemplate.opsForValue().get(cacheName + "customer_" + username);
if (o != null) {
customer = (Customer) o;
} else {
customer = customerRepository.findByUsername(username);
if (customer != null) {
redisTemplate.opsForValue().set(cacheName + "customer_" + username, customer);
}
}
return customer;
}
public List<Authority> getCustomerAuthority(String s) {
List<Authority> authorityList = null;
Object o = redisTemplate.opsForValue().get(cacheName + "authorities_" + s);
if (o != null) {
authorityList = (List<Authority>) o;
} else {
authorityList = authorityRepository.findAuthoritiesByUsername(s);
if (authorityList.size() > 0) {
redisTemplate.opsForValue().set(cacheName + "authorities_" + s, authorityList);
}
}
return authorityList;
}
}
- UserDetailsServiceImpl.java
- 这个是关键代码,用于封装认证用户信息,这个类中写的方法,主要针对Security来实现的
- 这里发现课本上有一个问题,他说的是重写,其实应该叫实现,因为如果是重写,那么 重写 方法的访问修饰符可以不同,但是验证有报错:
package wx0725.top.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 wx0725.top.domain.Authority;
import wx0725.top.domain.Customer;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author WEN
* @version 1.0
* @description: Wen Xuan
* @date 2021/5/8 下午 14:11
* @link http://wx0725.top
*
* 查询信息权限封装
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private CustomerService customerService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 获取用户信息
Customer customer = customerService.getCustomer(s);
// 获取权限
List<Authority> authorityList = customerService.getCustomerAuthority(s);
// 信息权限封装
List<SimpleGrantedAuthority> simpleGrantedAuthorityList = authorityList.stream()
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
.collect(Collectors.toList());
if (customer != null) {
UserDetails userDetails = new User(customer.getUsername(), customer.getPassword(), simpleGrantedAuthorityList);
return userDetails;
} else {
throw new UsernameNotFoundException("用户不存在");
}
}
}
- 最后就可以写认证了
- WebSecurityConfigurerAdapter.java
package wx0725.top.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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 wx0725.top.service.UserDetailsServiceImpl;
import javax.sql.DataSource;
/**
* @author WEN
* @version 1.0
* @description: Wen Xuan
* @date 2021/5/7 下午 19:42
* @link http://wx0725.top
*/
@EnableWebSecurity
// 上面一个注解中包含下面三个注解
//@Import
//@EnableGlobalAuthentication
//@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
public UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// 选择定义密码加密算法
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 内存认证使用这个密码加密
authenticationManagerBuilder.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder)
// 注册一个身份,并且密码需要加密
.withUser("user").password(bCryptPasswordEncoder.encode("user")).roles("common")
.and()
.withUser("admin").password(bCryptPasswordEncoder.encode("admin")).roles("common");
// 使用JDBC认证
// 查询客户
String usql = "select username,password,valid from t_customer where username=?";
// 查询客户对应的身份
String asql = "select c.username, a.authority from t_customer c,t_authority a,t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username=?";
// authenticationManagerBuilder.jdbcAuthentication().passwordEncoder(bCryptPasswordEncoder)
// .dataSource(dataSource)
// .usersByUsernameQuery(usql)
// .authoritiesByUsernameQuery(asql);
// UserDetailsServiceImpl
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
}
下面是缓存
此致,敬礼 🤷♀️