项目1-springsecutiry

SpringSecurity

简介

使用方式(这里用第三种方式)

  • 一种是全部利用配置文件,将用户、权限、资源(url)硬编码在xml文件中

  • 二种是用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置

  • 三种是细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器, 并分别实现AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。

SpringSecurity-HelloWorld

测试环境搭建

pom配置

       <dependency>

                     <groupId>org.springframework</groupId>

                     <artifactId>spring-webmvc</artifactId>

                     <version>4.3.20.RELEASE</version>

              </dependency>

              <dependency>

                     <groupId>javax.servlet.jsp</groupId>

                     <artifactId>jsp-api</artifactId>

                     <version>2.2</version>

                     <scope>provided</scope>

              </dependency>

              <dependency>

                     <groupId>javax.servlet</groupId>

                     <artifactId>servlet-api</artifactId>

                     <version>2.5</version>

                     <scope>provided</scope>

              </dependency>

              <dependency>

                     <groupId>javax.servlet</groupId>

                     <artifactId>jstl</artifactId>

                     <version>1.2</version>

              </dependency>


XML配置

<servlet>

<servlet-name>springDispatcherServlet</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath:spring.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>springDispatcherServlet</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>


Spring配置

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:context="http://www.springframework.org/schema/context"

xmlns:mvc="http://www.springframework.org/schema/mvc"

xsi:schemaLocation="http://www.springframework.org/schema/mvc 
<u>http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd</u>

<u>http://www.springframework.org/schema/beans</u> 
<u>http://www.springframework.org/schema/beans/spring-beans.xsd</u>

<u>http://www.springframework.org/schema/context</u> 
<u>http://www.springframework.org/schema/context/spring-context-4.3.xsd</u>">

<context:component-scan base-package="com.atguigu.security"></context:component-scan>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/views/"></property>

<property name="suffix" value=".jsp"></property>

</bean>

<mvc:annotation-driven />

<mvc:default-servlet-handler />

</beans>

引入SpringSecurity框架

添加security-pom依赖

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-web</artifactId>

<version>4.2.10.RELEASE</version>

</dependency>

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-config</artifactId>

<version>4.2.10.RELEASE</version>

</dependency>

<!-- 标签库 -->

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-taglibs</artifactId>

<version>4.2.10.RELEASE</version>

</dependency>

web.xml中添加SpringSecurity的Filter进行安全控制

<filter>

<filter-name>springSecurityFilterChain</filter-name><!--名称固定,不能变-->

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

加入SpringSecurity配置类

@Configuration、@Bean 注解作用
        
    @Configuration
    @EnableWebSecurity

    public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {

    }

SpringSecurity-实验

授权首页和静态资源

配置类(AppWebSecurityConfig extends WebSecurityConfigurerAdapter)
重写configure(HttpSecurity http)方法

@Configuration

@EnableWebSecurity

public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter { 

@Override

protected void configure(HttpSecurity http) throws Exception {

//super.configure(http); //取消默认配置 

http.authorizeRequests()

.antMatchers("/layui/**","/index.jsp").permitAll() //设置匹配的资源放行

.anyRequest().authenticated(); //剩余任何资源必须认证

}

}

静态资源和index.jsp都可以访问
不存在的资源
有权限无资源404

默认及自定义登录页

开启formLogin()功能  默认主页时
静态资源和index.jsp都可以访问

自定义表单登录逻辑分析

表单提交地址:${PATH }/index.jsp
表单提交请求方式:post
表单提交请求失败,提取错误消息:${SPRING_SECURITY_LAST_EXCEPTION.message}

自定义认证用户信息

auth.inMemoryAuthentication()

.withUser("zhangsan").password("123456").roles("ADMIN")

.and()

.withUser("lisi").password("123123").authorities("USER","MANAGER");


用户注销完成

添加注销功能(logout)http.logout()默认规则         
/logout:退出系统
如果csrf开启,必须post方式的/logout请求,表单中需要增加csrf token
logoutUrl();退出系统需要发送的请求
logoutSuccessUrl();退出系统成功以后要跳转的页面地址
addLogoutHandler():自定义注销处理器
deleteCookies():指定需要删除的cookie
invalidateHttpSession():session失效(DEBUG)

基于角色的访问控制

//设置资源可以访问的角色
http.authorizeRequests().antMatchers("/layui/**","/index.jsp").permitAll() //允许所有人都访问静态资源,无论登录(认证)与否

.antMatchers("/level1/**").hasRole("学徒")

.antMatchers("/level2/**").hasRole("大师")

.antMatchers("/level3/**").hasRole("宗师")

.anyRequest().authenticated(); //放置最后,以上没有规定的都需要权限认证。

//设置拥有该角色的资源可以访问

auth.inMemoryAuthentication()

.withUser("zhangsan").password("123456").roles("ADMIN","学徒","宗师")

.and()

.withUser("自定义访问拒绝处理页面,lisi").password("111111").authorities("USER","MANGER");

自定义访问拒绝处理页面

http.exceptionHandling().accessDeniedPage("/unauth.html");
<%@ page language="java" contentType="text/html; 
charset=UTF-8"

     pageEncoding="UTF-8"%>

<%

     pageContext.setAttribute("PATH", 
request.getContextPath());

%>

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<meta name="viewport"

     content="width=device-width, initial-scale=1, 
maximum-scale=1">

<title>武林秘籍管理系统</title>

<link rel="stylesheet" href="${PATH 
}/layui/css/layui.css">

</head> 

<body class="layui-layout-body">

     <div class="layui-layout layui-layout-admin">

          <!-- 顶部导航 -->

          <%@include file="/WEB-INF/include/navbar.jsp" %>

          

          <!-- 侧边栏 -->

          <%@include file="/WEB-INF/include/sidebar.jsp" 
%>

          

          

          <div class="layui-body">

              <!-- 内容主体区域 -->

              <div style="padding: 15px;">

                   <h1>你无权访问</h1>

              </div>

          </div>

          <div class="layui-footer"></div>

     </div>

     <script src="${PATH }/layui/layui.js"></script>

     <script src="${PATH 
}/layui/jquery-2.1.1.min.js"></script>

     <script>

          //JavaScript代码区域

          layui.use('element', function() {

              var element = layui.element;



          });

     </script>

</body>

</html>


http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {

@Override

public void handle(HttpServletRequest request, HttpServletResponse response,

AccessDeniedException accessDeniedException) throws IOException, ServletException {

request.setAttribute("message", accessDeniedException.getMessage());

request.getRequestDispatcher("/WEB-INF/views/unauth.jsp").forward(request, response);

}

});

记住我功能

简单版本

http.rememberMe();

数据库版本

  • 引入pom.xml包

    
        
    
    
    <dependency>
    <groupId>org.springframework</groupId>

    <artifactId>spring-orm</artifactId>

    <version>4.3.20.RELEASE</version>

    </dependency>

    <dependency>

    <groupId>com.alibaba</groupId>

    <artifactId>druid</artifactId>

    <version>1.1.12</version>

    </dependency>

    <!-- mysql驱动 -->

    <dependency>

        <groupId>mysql</groupId>

        <artifactId>mysql-connector-java</artifactId>

        <version>5.1.47</version>

    </dependency>
  • 配置数据源
<!-- 配置数据源 -->

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">

<property name="username" value="root"></property>

<property name="password" value="root"></property>

<property name="url" value="jdbc:mysql://192.168.137.3:3306/security?useSSL=false"></property>

<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>

</bean> 

<!--  jdbcTemplate-->

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

<property name="dataSource" ref="dataSource"></property>

</bean>


  • 创建表

  • 设置记住我

<!-- 配置数据源 -->

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">

<property name="username" value="root"></property>

<property name="password" value="root"></property>

<property name="url" value="jdbc:mysql://192.168.137.3:3306/security?useSSL=false"></property>

<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>

</bean> 

<!--  jdbcTemplate-->

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

<property name="dataSource" ref="dataSource"></property>

</bean>


认证

自定义UserDetailsService检索用户

package com.atguigu.security.component;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
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.Component;

import com.alibaba.druid.util.StringUtils;

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

	@Autowired
	JdbcTemplate jdbcTemplate;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		String queryUser = "SELECT * FROM `t_admin` WHERE loginacct=?";
		
		//1、查询指定用户的信息
		Map<String, Object> map = jdbcTemplate.queryForMap(queryUser, username);
	
		//查询用户拥有的角色集合
		String sql1="SELECT t_role.* FROM t_role LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role.id WHERE t_admin_role.adminid=?";                
		List<Map<String, Object>> roleList = jdbcTemplate.query(sql1, new ColumnMapRowMapper(), map.get("id"));
		System.out.println("roleList="+roleList); 
		
		//查询用户拥有的权限集合
		String sql2 = "SELECT distinct t_permission.* FROM t_permission LEFT JOIN t_role_permission ON t_role_permission.permissionid = t_permission.id LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role_permission.roleid WHERE t_admin_role.adminid=?";
		List<Map<String, Object>> permissionList = jdbcTemplate.query(sql2, new ColumnMapRowMapper(), map.get("id"));
		 
		System.out.println("permissionList="+permissionList);
		
		//用户权限=【角色+权限】
		Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
		                
		for (Map<String, Object> rolemap : roleList) {
		String rolename = rolemap.get("name").toString();
		authorities.add(new SimpleGrantedAuthority("ROLE_"+rolename));
		}   
		
		
		for (Map<String, Object> permissionmap : permissionList) {
			String permissionName = permissionmap.get("name").toString();
			if(!StringUtils.isEmpty(permissionName)) {
			authorities.add(new SimpleGrantedAuthority(permissionName));
			}                        
		}
		
		System.out.println("authorities="+authorities); 
		
		//return new User(map.get("loginacct").toString(),map.get("userpswd").toString(),
		//AuthorityUtils.createAuthorityList("ADMIN","USER"));
		return new User(map.get("loginacct").toString(),map.get("userpswd").toString(),authorities);
		
	}

自定义实现类---》Dao层(调用实现类)--》数据库查询的封装到Userdetails对象中
页面提交的账号密码封装到Authentication对象中
将密码加密后与数据库中密码进行比对

基于数据库(MD5密码)认证 (debug)

配置configure

    //测试:分析源码(验证密码不一致)
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);

引入MD5加密工具
PasswordEncoder接口实现类

package com.atguigu.security.component;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class PasswordEncoderImpl implements PasswordEncoder {

	@Override
	public String encode(CharSequence rawPassword) {
		// TODO Auto-generated method stub
		return MD5Util.digest(rawPassword.toString());
	}

	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		// TODO Auto-generated method stub
		return encodedPassword.equals(MD5Util.digest(rawPassword.toString()));
	}

}



基于数据库(BCryptPasswordEncoder)密码加密认证

细粒度权限控制

前置细节【Role和Authority的区别】

用户拥有的权限表示

roles("ADMIN","学徒","宗师") 
authorities("USER","MANAGER");

给资源授予权限(角色或权限)

//.antMatchers("/level1/**").hasRole("学徒")

//.antMatchers("/level1/**").hasAnyRole("学徒","ADMIN")//拥有任何一个角色都可以访问

.antMatchers("/level1/**").hasAnyAuthority("学徒","ADMIN") //拥有任何一个权限都可以访问

.antMatchers("/level2/**").hasRole("大师")

.antMatchers("/level3/**").hasRole("宗师")
roles("ADMIN","学徒","宗师") 

增加"ROLE_"前缀存放:【"ROLE_ADMIN","ROLE_学徒","ROLE_宗师"】

表示拥有的权限。一个角色表示的是多个权限

用户传入的角色不能以ROLE_开头,否则会报错。ROLE_是自动加上的

如果我们保存的用户的角色:直接传入角色的名字,权限【new 
SimpleGrantedAuthority("ROLE_" + role)】保存即可



authorities("USER","MANAGER");
原样存放:【"USER","MANAGER"】

表示拥有的权限。

如果我们保存的是真正的权限;直接传入权限名字,权限【new SimpleGrantedAuthority(role)】保存

无论是Role还是Authority都保存在  List<GrantedAuthority>,每个用户都拥有自己的权限集合->List<GrantedAuthority>


细粒度的资源控制

authenticated():通过认证的用户都可以访问
permitAll():允许所有人访问,即使未登录
authorizeRequests():更细粒度的控制
access(String): SpEL:Spring表达式
.access("hasRole('大师') AND hasAuthority('user:delete') OR hasIpAddress('192.168.50.15')")

细粒度的资源控制相应注解

@EnableWebSecurity:开启 Spring Security 注解

@EnableGlobalMethodSecurity(prePostEnabled=true):开启全局的细粒度方法级别权限控制功能



@PreAuthorize("hasRole('ADMIN')")  

public void addUser(User user){  

    //如果具有ROLE_ADMIN 权限 则访问该方法  

    ....  

}


@PostAuthorize:允许方法调用,但是,如果表达式结果为false抛出异常  
    //returnObject可以获取返回对象user,判断user属性username是否和访问该方法的用户对象的用户名一样。不一样则抛出异常。  

    @PostAuthorize("returnObject.user.username==principal.username")  

    public User getUser(int userId){  

       //允许进入

    ...  

        return user;

    }

    

//将结果过滤,即选出性别为男的用户  

@PostFilter("returnObject.user.sex=='男' ")  

public List<User> getUserList(){  

    //允许进入

    ...  

    return user; 

}


@PreFilter:允许方法调用,但必须在进入方法前过滤输入值


@Secured('ADMIN')   等价于    @PreAuthorize("hasRole('ADMIN')") 


细粒度权限控制实现步骤

https://docs.spring.io/spring-security/site/docs/4.0.1.RELEASE/reference/htmlsingle/#el-common-built-in
开启全局的细粒度方法级别权限控制功能

@EnableGlobalMethodSecurity(prePostEnabled = true)
    @EnableWebSecurity

    @Configuration

    public class AppSecurityConfig extends WebSecurityConfigurerAdapter {

    }

将手动授权的方式注释掉

//.antMatchers("/level1/**").hasRole("学徒")

//.antMatchers("/level1/**").hasAnyRole("学徒","ADMIN")

//.antMatchers("/level1/**").hasAnyAuthority("学徒","ADMIN")

//.antMatchers("/level1/**").hasAuthority("学徒")

//.antMatchers("/level2/**").hasRole("大师")

//.antMatchers("/level3/**").hasRole("宗师")  


给访问资源的方法增加注解,进行访问授权

package com.atguigu.security.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
public class GongfuController {
	
	@PreAuthorize("hasRole('学徒') AND hasAuthority('luohan')")
	@GetMapping("/level1/1")
	public String leve1Page(){
		return "/level1/1";
	}
	
	@PreAuthorize("hasRole('学徒') AND hasAuthority('wudang')")
	@GetMapping("/level1/2")
	public String leve2Page(){
		return "/level1/2";
	}
	
	@PreAuthorize("hasRole('学徒') AND hasAuthority('quanzhen')")
	@GetMapping("/level1/3")
	public String leve3Page(){
		return "/level1/3";
	}

}

通过数据库加载用户权限

package com.atguigu.security.component;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
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.Component;

import com.alibaba.druid.util.StringUtils;

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

	@Autowired
	JdbcTemplate jdbcTemplate;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		String queryUser = "SELECT * FROM `t_admin` WHERE loginacct=?";
		
		//1、查询指定用户的信息
		Map<String, Object> map = jdbcTemplate.queryForMap(queryUser, username);
	
		//查询用户拥有的角色集合
		String sql1="SELECT t_role.* FROM t_role LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role.id WHERE t_admin_role.adminid=?";                
		List<Map<String, Object>> roleList = jdbcTemplate.query(sql1, new ColumnMapRowMapper(), map.get("id"));
		System.out.println("roleList="+roleList); 
		
		//查询用户拥有的权限集合
		String sql2 = "SELECT distinct t_permission.* FROM t_permission LEFT JOIN t_role_permission ON t_role_permission.permissionid = t_permission.id LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role_permission.roleid WHERE t_admin_role.adminid=?";
		List<Map<String, Object>> permissionList = jdbcTemplate.query(sql2, new ColumnMapRowMapper(), map.get("id"));
		 
		System.out.println("permissionList="+permissionList);
		
		//用户权限=【角色+权限】
		Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
		                
		for (Map<String, Object> rolemap : roleList) {
		String rolename = rolemap.get("name").toString();
		authorities.add(new SimpleGrantedAuthority("ROLE_"+rolename));
		}   
		
		
		for (Map<String, Object> permissionmap : permissionList) {
			String permissionName = permissionmap.get("name").toString();
			if(!StringUtils.isEmpty(permissionName)) {
			authorities.add(new SimpleGrantedAuthority(permissionName));
			}                        
		}
		
		System.out.println("authorities="+authorities); 
		
		//return new User(map.get("loginacct").toString(),map.get("userpswd").toString(),
		//AuthorityUtils.createAuthorityList("ADMIN","USER"));
		return new User(map.get("loginacct").toString(),map.get("userpswd").toString(),authorities);
		
	}
}

准备数据

查询用户拥有的角色集合 
    SELECT 
      t_role.* 

    FROM

      t_role 

      LEFT JOIN t_admin_role 

        ON t_admin_role.roleid = t_role.id 

    WHERE t_admin_role.userid = 1


    查询用户拥有的权限集合
    SELECT DISTINCT 
      t_permission.* 

    FROM

      t_permission 

      LEFT JOIN t_role_permission 

        ON t_role_permission.permissionid = 
    t_permission.id 

      LEFT JOIN t_admin_role 

        ON t_admin_role.roleid = 
    t_role_permission.roleid 

    WHERE t_admin_role.userid = 1

SpringSecurity-原理

初始化方法

过滤器:功能扩展的多个过滤器->责任链设计模式

获取过滤器链中的过滤器,封装为虚拟的VirtualFilterChain对象,并开始执行过滤

开始一个一个的执行过滤器

不同过滤器介绍(直接看文档)

UsernamePasswordAuthenticationFilter认证原理

执行过滤器,获取到页面的用户名和密码

将username和password包装成UsernamePasswordAuthenticationToken

获取系统的认证管理器(AuthenticationManager)来调用authenticate方法完成认证(this.getAuthenticationManager().authenticate(authRequest))
AuthenticationManager获取ProviderManager来调用ProviderManager.authenticate()

ProviderManager获取到所有的AuthenticationProvider判断当前的提供者能否支持,如果支持provider.authenticate(authentication);
DaoAuthenticationProvider( authentication :页面封装用户名和密码的对象)

3.2.1)、retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);

3.2.1.1)、 loadedUser = this.getUserDetailsService().loadUserByUsername(username);

(调用我们自己的UserDetailsService来去数据库查用户,按照用户名查出来的用户的详细信息)封装成UserDetail

3.2.2)、 preAuthenticationChecks.check(user);(预检查,账号是否被锁定等…)

3.2.3)、 additionalAuthenticationChecks(附加的认证检查)

3.2.3.1)、使用passwordEncoder. matches检查页面的密码和数据库的密码是否一致

3.2.4)、 postAuthenticationChecks.check(user);(后置认证,检查密码是否过期)

3.2.5)、 createSuccessAuthentication:将认证成功的信息重新封装成UsernamePasswordAuthenticationToken

3.3)、 3.2又返回了一个新的UsernamePasswordAuthenticationToken,然后擦掉密码

eventPublisher.publishAuthenticationSuccess(result);认证成功


successfulAuthentication(request, response, chain, authResult);
通过调用 SecurityContextHolder.getContext().setAuthentication(...)  将 Authentication 对象赋给当前的 SecurityContext

认证流程



用户使用用户名和密码登录



用户名密码被过滤器(默认为 UsernamePasswordAuthenticationFilter)获取到,封装成 
Authentication(UsernamePasswordAuthenticationToken)



token(Authentication实现类)传递给 AuthenticationManager 进行认证



AuthenticationManager 认证成功后返回一个封装了用户权限信息的 Authentication 对象



通过调用 SecurityContextHolder.getContext().setAuthentication(...)  将 Authentication 对象赋给当前的 SecurityContext 



将用户的信息保存到当前线程上,共享起来



SecurityContextHolder.getContext();就能获取到之前认证好的Authentication对象(UsernamePasswordAuthenticationToken


原文地址:https://www.cnblogs.com/suit000001/p/13848830.html