使用JDBC、UserDetailsService方式实现身份验证

spring security 两个作用:

  1. 你是谁:who are you?
    专业一点叫: Authentication(认证):
    a. 内存认证
    b. jdbc认证
    c. UserDetailsService认证
    d. ldap 认证
    下面是源码中的四个认证方法
    使用JDBC、UserDetailsService方式实现身份验证_第1张图片

  2. 你能干什么:what are you allwoed to do?

使用JDBC、UserDetailsService方式实现身份验证_第2张图片

pom.xml


<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.0modelVersion>

    <groupId>wx0725.topgroupId>
    <artifactId>guojihuaartifactId>
    <version>1.0.0version>

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.1.3.RELEASEversion>
        <relativePath/> 
    parent>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <java.version>1.8java.version>
    properties>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
            <optional>trueoptional>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>


        
        <dependency>
            <groupId>commons-iogroupId>
            <artifactId>commons-ioartifactId>
            <version>2.6version>
        dependency>


        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>

        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druid-spring-boot-starterartifactId>
            <version>1.1.10version>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>




    dependencies>


    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <version>2.1.3.RELEASEversion>
            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>
        
        
        
        <title>首页title>
    head>
    <body>
        <h1>hello world!h1>
    body>
html>

运行项目
输入:127.0.0.1:8082 也就是直接访问 spring boot 项目地址
此时会自动跳珠到login

使用JDBC、UserDetailsService方式实现身份验证_第3张图片
可以看到login页面是从127.0.0.1:8082重定向过去的
使用JDBC、UserDetailsService方式实现身份验证_第4张图片

使用JDBC、UserDetailsService方式实现身份验证_第5张图片

2. JDBC 认证

需要新建一个安全检测的表格:

客户表:
使用JDBC、UserDetailsService方式实现身份验证_第6张图片

身份表:
使用JDBC、UserDetailsService方式实现身份验证_第7张图片

客户对应的身份表
使用JDBC、UserDetailsService方式实现身份验证_第8张图片

在内存认证的配置类中如下代码:

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
如果你输入的是其他的页面,在登录成功之后会跳转到那个页面,并非跳转到首页。
使用JDBC、UserDetailsService方式实现身份验证_第9张图片

3.UserDetailsService

这一部分代码不少:
其主要实现的是减少数据库压力,通过用户缓存信息,实现验证登录,在登录的时候只查询一次数据库,将其保存在缓存中,下次查询就直接读取缓存。

  1. 新建三个实体类
    分别对应上面的三个表格,但是这里没有用到用户权限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
 * 

* 用户权限 */ @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; } }

  1. 还需对应写两个接口:
  • 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
 * 

* 用户信息查询接口 */ 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);

}
  1. 接下来就是具体的实现操作,也就是业务层
    对接实体层与控制层
  • 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
 * 

* 业务处理 * 用来通过用户名查询用户信息、权限 */ @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来实现的
  • 使用JDBC、UserDetailsService方式实现身份验证_第10张图片
  • 在这里插入图片描述
  • 这里发现课本上有一个问题,他说的是重写,其实应该叫实现,因为如果是重写,那么 重写 方法的访问修饰符可以不同,但是验证有报错:
  • 在这里插入图片描述
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("用户不存在");
        }
    }
}


  1. 最后就可以写认证了
  • 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);

    }
}

下面是缓存
在这里插入图片描述

使用JDBC、UserDetailsService方式实现身份验证_第11张图片
此致,敬礼 ‍♀️

你可能感兴趣的:(springboot,Java)