接着说,接着说🧖♀️代码资源在下面💇♀️
上一期传送门在这里:玩转SpringBoot安全管理:SpringSecurity介绍及入门、自定义用户认证及授权管理、MVC Security安全配置介绍(内存和JDBC身份认证实现)
这一章节会比较困难🦹♂️🦹♂️🦹♂️
链接:https://pan.baidu.com/s/12T_vgCH9AXt796YpccAYDg
提取码:6666
对于用户量比较大的项目来说呢,频繁使用JDBC进行数据库查询认证非常麻烦,会降低网站登陆速度,对于一个完善的项目来说,如果有一些业务已经实现了用户信息查询的服务,那么就没有必要再去使用JDBC查询了
那么现在在我们的项目里建立定义查询用户及角色信息的服务接口
修改pom文件,这里不演示👱♀️
Authority.class
package com.security.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
@Entity(name = "t_authority ")
public class Authority implements Serializable {
// 由于使用了缓存,所以一定要对实体类进行序列化
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String 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;
}
@Override
public String toString() {
return "Authority{" +
"id=" + id +
", authority='" + authority + '\'' +
'}';
}
}
Customer .class
package com.security.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity(name = "t_customer")
public class Customer implements Serializable {
// 由于使用了缓存,所以一定要对实体类进行序列化
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
private Boolean valid;
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", valid=" + valid +
'}';
}
public Boolean getValid() {
return valid;
}
public void setValid(Boolean valid) {
this.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;
}
}
这里使用的是JPA,不是mybatisPlus,没关系,sql都比较简单,无妨
package com.security.repository;
import com.security.domain.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CustomerRepository extends JpaRepository<Customer,Integer> {
Customer findByUsername(String username);
}
package com.security.repository;
import com.security.domain.Authority;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
/**
* Created by admin on 2018-11-19.
*/
public interface AuthorityRepository extends JpaRepository<Authority,Integer> {
@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)
public List<Authority> findAuthoritiesByUsername(String username);
}
package com.security.service;
import com.security.domain.Customer;
import com.security.domain.Authority;
import com.security.repository.CustomerRepository;
import com.security.repository.AuthorityRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Classname CustomerService
* @Description 对用户数据结合Redis缓存进行业务处理
*/
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
@Autowired
private AuthorityRepository authorityRepository;
@Autowired
private RedisTemplate redisTemplate;
// 业务控制:使用唯一用户名查询用户信息
// 一开始先去缓存中查,查不到再去数据库查
public Customer getCustomer(String username){
Customer customer=null;
Object o = redisTemplate.opsForValue().get("customer_"+username);
if(o!=null){
customer=(Customer)o;
}else {
customer = customerRepository.findByUsername(username);
if(customer!=null){
redisTemplate.opsForValue().set("customer_"+username,customer);
}
}
return customer;
}
// 业务控制:使用唯一用户名查询用户权限
public List<Authority> getCustomerAuthority(String username){
List<Authority> authorities=null;
Object o = redisTemplate.opsForValue().get("authorities_"+username);
if(o!=null){
authorities=(List<Authority>)o;
}else {
authorities=authorityRepository.findAuthoritiesByUsername(username);
if(authorities.size()>0){
redisTemplate.opsForValue().set("authorities_"+username,authorities);
}
}
return authorities;
}
}
UserDetailsService是security提供的进行认证用户信息封装的接口,该接口只提供了一个方法:loadUserByUsername,该方法用户通过用户名加载用户信息,使用这个接口时,需要自定义一个实现类,通过这个方法调用用户业务处理类中已有的方法进行用户详情信息封装,该方法返回类型UserDetails,它也是一个接口,用来供security认证使用
又来一波重点
由于UserDetails是一个接口,所以我们没有办法new出它的实例对象,所以在这里new的是他的一个子类User,User就是UserDetails的一个实现类,User构造方法:
它需要传入用户名、密码以及一个Collection
这里的Collection是一个权限集合,它的泛型是GrantedAuthority[是一个接口],它的实现类有三个:
在这里我们使用的是第二个:SimpleGrantedAuthority
package com.security.service;
import com.security.domain.Authority;
import com.security.domain.Customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.*;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Classname UserDetailsServiceImpl
* @Description 自定义一个UserDetailsService接口实现类进行用户认证信息封装
*/
@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> authorities = customerService.getCustomerAuthority(s);
// 对用户权限进行封装
List<SimpleGrantedAuthority> list = authorities.stream().map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList());
// 返回封装的UserDetails用户详情类
if(customer!=null){
UserDetails userDetails= new User(customer.getUsername(),customer.getPassword(),list);
return userDetails;
} else {
// 如果查询的用户不存在(用户名不存在),必须抛出此异常
throw new UsernameNotFoundException("当前用户不存在!");
}
}
}
package com.security.config;
import com.security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
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;
/**
* @program: spring security
* @description: security配置类
* @author: xmonster_大魔王
* @create: 2022-08-02 11:41
**/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Qualifier("dataSource")
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 设置密码编码器
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 使用userDetailsService进行身份认证
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
}
先输入错误的:
正确的:❣❣❣
先来登录admin,admin的权限是common
登录李四试试~
真香