一.springsecurity简单介绍
1.SpringSecurity基于Spring框架,为web应用中的权限控制提供了一套解决方案。使用springsecurity可以简化系统中权限控制相关功能的开发
二.普通例子
2.1创建springboot工程名为SpringBoot_Security
1)配置Maven
2)导入web的starter,以下为pom文件部分代码
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.4.5</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.2在java目录下创建包为com/hhh/controller,其包下创建类为ResourcesController
package com.hhh.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/res")
public class ResourcesController {
@GetMapping("/res1")
public String res1(){
return "res1===>";
}
@GetMapping("/res2")
public String res2(){
return "res2===>";
}
}
2.3在resources目录下创建配置文件application.properties
server.port=8081
2.4在com/hhh包下创建启动类AppApplication
package com.hhh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
//启动类
@SpringBootApplication
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class,args);
}
}
2.5启动工程,在浏览器中访问,查看效果
三.入门案例
采用springboot工程来整合spring、,security,可以简化开发步骤
3.1导入spring-security的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<!--已经通过上边的父工程指定了版本号-->
</dependency>
刷新工程
3.2这时重新启动工程再次访问就有权限控制
3.3控制台有显示密码,默认的用户名为user
3.4输入正确用户名和密码后即可访问成功
四.修改用户名和密码
4.1在内存中配置一个用户,这里在配置类中配置
1)新建一个包com/hhh/config,其包下新建配置类SecurityConfig
package com.hhh.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.stereotype.Controller;
@Controller
public class SecurityConfig {
//在内存中配置一个用户
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager=new InMemoryUserDetailsManager();
/*用户名为aaa,密码123456,权限为p1*/
manager.createUser(User.withUsername("aaa").password("123456").authorities("p1").build());
return manager;
}
/*配置编码器,框架要求必须有编码器,这里做的编码器不做编码*/
@Bean
public PasswordEncoder passwordEncoder(){
//这个编码器相当于不做编码
return NoOpPasswordEncoder.getInstance();
}
}
2)重新启动工程,按照配置的用户名和密码,即可访问成功
3)访问成功
4.2UserDetailService
UserDetailService是一个接口,springsecurity通过这个接口来查找用户信息,和前端传递过来的用户信息进行比较
五.springsecurity的安全配置
1.直接访问登录页面(localhost:8081/index.html),如果工程中没有静态资源存放的文件的index.html文件,报404
2.在webapp目录下新建文件index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>
3.直接访问登录页面
登录成功后会跳转到首页(index.html)
4.springsecurity的安全配置
通过在配置类中继承WebSecurityConfigureAdapter类,重写其中的configure(HttpSecurity http)方法来调整调整springsecurity的配置
4.1SecurityConfig新添代码
package com.hhh.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.stereotype.Controller;
@Controller
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//在内存中配置一个用户
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager=new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("aaa").password("123456").authorities("p1").build());
return manager;
}
/*配置编码器,框架要求必须有编码器,这里做的编码器不做编码*/
@Bean
public PasswordEncoder passwordEncoder(){
//这个编码器相当于不做编码
return NoOpPasswordEncoder.getInstance();
}
/*重写其中的configure(HttpSecurity http)方法来调整调整springsecurity的配置*/
@Override
protected void configure(HttpSecurity http) throws Exception {
/*访问该路径受权限控制,authenticated()表示登录就能访问*/
http.authorizeRequests().antMatchers("/res/res1").authenticated();
/*其他路径放行*/
http.authorizeRequests().anyRequest().permitAll();
http.formLogin();
}
}
4.2启动工程,访问res/res2正常访问
4.3访问res/res1受到权限控制
4.4输入对应用户名和密码即可访问成功
4.5配置某些资源需要特定的权限才能访问,SecurityConfig部分代码
@Override
protected void configure(HttpSecurity http) throws Exception {
/*访问该路径受权限控制*/
http.authorizeRequests().antMatchers("/res/res1").authenticated();
/*配置某些资源需要特定的权限才能访问*/
http.authorizeRequests().antMatchers("/res/res2").hasAuthority("p2");
/*在上方创建用户时设置了p1权限,这里指定了p2权限,即访问res2失败*/
/*其他路径放行*/
http.authorizeRequests().anyRequest().permitAll();
http.formLogin();
}
4.6启动工程,访问res2查看效果,报403错误
4.7可以在配置用户那配置p2权限,这样重新启动工程,res2也可以被访问到
4.8注意:配置路径匹配规则时,越详细的路径配置规则写在前面
因为一次请求匹配到第一个规则后就会忽略后边的规则,如下
@Override
protected void configure(HttpSecurity http) throws Exception {
/*模糊匹配路径设置在前头,访问res2就不需要p2权限*/
http.authorizeRequests().antMatchers("/res/**").authenticated();
/*访问该路径受权限控制*/
http.authorizeRequests().antMatchers("/res/res1").authenticated();
/*配置某些资源需要特定的权限才能访问*/
http.authorizeRequests().antMatchers("/res/res2").hasAuthority("p2");
/*在上方创建用户时设置了p1权限,这里指定了p2权限,即访问res2失败*/
/*其他路径放行*/
http.authorizeRequests().anyRequest().permitAll();
http.formLogin();
}
忽略后面规则,访问成功
4.9配置退出
1)默认url:/logout
2)点击Log Out后回到登录页面
3)自定义配置,configure方法里的部分代码
http.csrf().disable(); //关闭csrf
/*
中间路径权限控制
*/
//配置退出
http.logout().logoutUrl("/out");
4)启动工程,登录成功后,使用配置的退出即可
5)在webapp目录下新建文件logoutSuccess.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面</title>
</head>
<body>
<h1>退出成功</h1>
</body>
</html>
6)配置退出后跳转到其他页面
//配置退出后跳转的页面
http.logout().logoutUrl("/out").logoutSuccessUrl("/logoutSuccess.html");
7)启动工程,登录成功后,退出该页面,查看效果
六.springsecurity自定义登录页面和方法级权限控制
1.在webapp目录下创建文件为login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<style>
.center {
display: flex;
width: 350px;
margin: 0 auto;
margin-top: 100px;
height: 220px;
background-color: blanchedalmond;
}
form {
width: 320px;
margin: 0 auto;
}
.user-name {
display: flex;
margin-top: 20px;
justify-content: space-between;
align-items: center;
}
.user-name input {
width: 220px;
height: 30px;
}
.password {
margin-top: 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.password input {
width: 220px;
height: 30px;
}
.btn {
margin-top: 30px;
display: flex;
}
.btn input {
width: 320px;
height: 30px;
}
</style>
</head>
<body>
<div class="center">
<form action="/login" method="post">
<div class="user-name">
<div>
用户名
</div>
<div>
<input type="text" name="username">
</div>
</div>
<div class="password">
<div>
密码
</div>
<div>
<input type="password" name="password">
</div>
</div>
<div class="btn">
<input type="submit" value="提交">
</div>
</form>
</div>
</body>
</html>
2.配置自定义登录页面,以部分代码下为SecurityConfig类中configure方法中的部分代码
/*配置自定义登录页面,前面为静态资源的存放路径,后面为登录提交到哪*/
http.formLogin().loginPage("/login.html").loginProcessingUrl("/login");
/*注意配置登录页面(login.html)里的action属性和method属性*/
3.启动工程访问res1,查看效果
4.输入正确的用户名和密码正常访问到res1,表明设置成功
5.方法级权限控制
5.1在ResourcesController类中新添一个方法res3
@GetMapping("/res3")
public String res3(){
return "res3===>";
}
让res3这个方法受到p1的权限控制有两种方法:
第一,在SecurityConfig类中configure方法中添加对应规则
5.2新建包com/service,其包下新建类为ResourceService
package com.hhh.service;
import org.springframework.stereotype.Service;
@Service
public class ResourceService {
public String res3(){
return "success";
}
}
5.3在ResourcesController类中注入ResourceService并在res3方法中返回该类中的res3方法,以下为ResourcesController的部分代码
@Autowired
private ResourceService resourceService;
@GetMapping("/res3")
public String res3(){
return resourceService.res3();
}
5.4这里控制ResourceService类中的res3方法,让其有p1的权限才能份访问
做法:在安全配置类(SecurityConfig上)加上注解@EnableGlobalMethodSecurity开启方法权限控制
5.5在需要进行权限控制的方法加上 注解@PreAuthorize("hasAuthority('p1')")
package com.hhh.service;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class ResourceService {
//要有p1权限才能访问
@PreAuthorize("hasAuthority('p1')")
public String res3(){
return "success";
}
}
5.6启动工程,访问res3,受到控制不能直接访问
5.7填写正确用户名和密码即可访问成功
七.springsecurity配置从数据库获取用户
1.导入mybatis,mysql驱动的依赖并刷新依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
2.在resources目录下新建包为com/hhh/mapper,其下新建文件UserDao.xml,用来写sql语句,这里先写一个框架,具体sql后续写上
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
</mapper>
3.将数据库的连接信息在配置文件(application.properties)中配置上
#端口设置
server.port=8081
#数据库连接设置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ssmframe
spring.datasource.username=root
spring.datasource.password=hyt123456
#别名设置和到sql文件扫描路径的配置
mybatis.type-aliases-package=com.hhh.domain
mybatis.mapper-locations=classpath:com/hhh/mapper/*.xml
4.新建包为com/hhh/domain,其包下新建类UserInfo,用来映射用户信息的
package com.hhh.domain;
public class UserInfo {
private int id;
private String username;
private String passwd;
//权限
private String auth;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getAuth() {
return auth;
}
public void setAuth(String auth) {
this.auth = auth;
}
}
5.在数据库环境中创建数据库
use ssmframe;
create table t_user(
id int primary key auto_increment,
username varchar(30),
passwd varchar(128),
auth varchar(128)
);
insert into t_user(username,passwd,auth)values
('aaaaa','123456','p1,p2');
select * from t_user;
6.在service层新建类为MyUserDetailsService,实现接口UserDetailsService
package com.hhh.service;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//查询数据库,获取用户信息
return null;
}
}
7.新建包com/hhh/dao,其包下新建接口UserDao
package com.hhh.dao;
import com.hhh.domain.UserInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao {
public UserInfo findByUsername(String username);
}
8.完善UserDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--dao层接口的全路径-->
<mapper namespace="com.hhh.dao.UserDao">
<!--id值为UserDao类中的方法名,参数类型和返回值类型-->
<select id="findByUsername" parameterType="String" resultType="userInfo">
select * from t_user where username=#{username};
</select>
</mapper>
9.继续处理MyUserDetailsService
package com.hhh.service;
import com.hhh.dao.UserDao;
import com.hhh.domain.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Service
public class MyUserDetailsService implements UserDetailsService {
//用到dao层接口,注入
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//查询数据库,获取用户信息
UserInfo userInfo = userDao.findByUsername("username");
String auth=userInfo.getAuth(); //得到权限
String[] split = auth.split(",");//用逗号分割,得到一个数组
//数据库存的是逗号隔开去存的 ,这里是单个存的
return User.withUsername(userInfo.getUsername())
.password(userInfo.getPasswd())
.authorities(split)
.build();
}
}
10.注释SecurityConfig类中配置的用户,启动工程,访问res1查看效果
11.输入从数据库中设置的用户名和密码 ,查看效果(登录失败)
加断点调试
八.springsecurity对用密码进行加密
1.在SecurityConfig类中进行对密码加密
2.在com/hhh包下创建测试类Test,用来测试这个接口(PasswordEncoder)如何使用
package com.hhh;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class Test {
public static void main(String[] args) {
BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
String password="123";
//密文
String encode = passwordEncoder.encode(password);
System.out.println(encode);
}
}
3.运行这个程序,在控制台中查看
4.查看匹配机制
package com.hhh;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class Test {
public static void main(String[] args) {
BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
String password="123";
//密文
String encode = passwordEncoder.encode(password);
System.out.println(encode);
//匹配,同样发明文通过计算是不一样的,但是matches方法传的值是第一次或第二次都是true
//这里数据库中就可以更改密码为密文
boolean matches = passwordEncoder.matches(password, encode);
System.out.println(matches);
}
}
5.更改数据库中数据表t_user中设置的passwd
6.SecurityConfig类中返回密文的编码器
7.启动工程,登录res1,输入正确用户名和密码即可登录成功,而此时数据库密码存着是密文
九.springsecurity实现下次自动登录功能
原理:勾选“记住我”复选框,登录成功后,springsecurity会创建出一个token字符串和当前用户关联,保存这个管理关系;根据token创建一个cookie发送给客户端。客户端下次访问时如果携带了这个cookie,security就会验证这个cookie,验证成功就会完成自动登录。
1.添加依赖
<!--新添依赖,用来实现自动登录-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
2.前端页面login.html中新添复选框
3.在内存里保存这个关联关系,在SecurityConfig类中操作
//这里的isRemember与login.html中复选框的name属性值一致,后面为有效期,单位为秒
http.rememberMe().rememberMeParameter("isRemember").tokenValiditySeconds(30);
4.启动工程,登录res1,查看效果
5.在数据库里保存这个关联关系,在SecurityConfig类中操作
//在数据库中存关联关系
http.rememberMe().rememberMeParameter("isRemember")
.tokenValiditySeconds(30)
.tokenRepository(jdbcTokenRepository());
}
//注入连接池对象
@Autowired
private DataSource dataSource;
public PersistentTokenRepository jdbcTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository=new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
//启动的时候要不要自己创建表,名为persistent_logins
tokenRepository.setCreateTableOnStartup(true);
//没有创建表时,使用true,第二次启动工程时记得改为false
return tokenRepository;
}
6.启动工程,登录res1,查看效果
十.springsecurity认证原理分析
spring security是基于过滤器来实现认证和授权的
1.入口过滤器
FilterChainProxy这个过滤器中维护了一个过滤器链,请求到来时,会依次使用过滤器链中的过滤器对请求进行处理
private List<SecurityFilterChain> filterChains;
这个SecurityFilterChain接口用来封装过滤器列表
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
在FilterChainProxy的doFilter方法中会使用从SecurityFilterChain中获取到的过滤器来对请求进行处理。
通过内部类 VirtualFilterChain来实现多个过滤器依次处理请求。
2.过滤器链中重要的过滤器介绍
(1)UsernamePasswordAuthenticationFilter 用来处理表单登录的过滤器
表单登录的过程:
a.用户提交用户名和密码,被此过滤器拦截到,将用户信息封装为接口Authentication 对象,
实际是UsernamePasswordAuthenticationToken这个实现类的对象
b.将Authentication对象提交到认证管理器AuthenticationManager进行认证
c.认证成功后AuthenticationManager会返回一个填充了用户信息的Authentication对象。
d.SecurityContextHolder将设置了用户信息的Authentication对象设置到其内部。
认证管理器:
AuthenticationManager:
实现类:ProviderManager
其中维护了AuthenticationProvider列表,请求到来时会循环维护的这个列表,选择一个可用的Provider进行认证
AuthenticationProvider也是一个接口,实现类:DaoAuthenticationProvider,用来处理从数据库获取用户进行认证的情况
(2)RememberMeAuthenticationFilter:处理通过记住我进行登录的请求
十一.前后端分离下使用spring security
1.前后端分离开发的售后,前端通过ajax技术调用后端接口,ajax不能处理后端返回的重定向请求,轻易需要调整springsecurity的配置,让后端给前端返回json数据,前端自己判断认证的结果进行路由跳转。
2.在工程中webapp目录下添加测试文件ajaxTest.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/axios.js"></script>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="res1">请求res1</button>
<button @click="res2">请求res2</button>
<hr>
<textarea v-model="resultData"></textarea>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!',
resultData:''
},
created(){
},
methods:{
res2(){
axios.post('/res/res2').then(res=>{
if(res.data.code=='401'){
//前端控制跳转逻辑
alert('未登录')
//location.href='http://localhost/login.html'
}else if(res.data.code=='403'){
alert("无权限访问");
}else{
console.log(res.data.data);
this.resultData=res.data
}
})
},
res1(){
axios.get('/res/res1').then(res=>{
if(res.data.code=='401'){
//前端控制跳转逻辑
//location.href='http://localhost/login.html'
alert('未登录');
}else if(res.data.code=='403'){
alert("无权限访问");
}else{
this.resultData=res.data
}
})
}
}
});
</script>
</body>
</html>
3.在webapp下新建文件夹为js,里面新建放置两个ajax的库和前端框架的文件
3.1axios.js
/* axios v0.25.0 | (c) 2022 by Matt Zabriskie */
/* axios v0.24.0 | (c) 2022 by Matt Zabriskie */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=11)}([function(e,t,n){"use strict";var r=n(3),o=Object.prototype.toString;function i(e){return"[object Array]"===o.call(e)}function s(e){return void 0===e}function a(e){return null!==e&&"object"==typeof e}function u(e){if("[object Object]"!==o.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function c(e){return"[object Function]"===o.call(e)}function f(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),i(e))for(var n=0,r=e.length;n<r;n++)t.call(null,e[n],n,e);else for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&t.call(null,e[o],o,e)}e.exports={isArray:i,isArrayBuffer:function(e){return"[object ArrayBuffer]"===o.call(e)},isBuffer:function(e){return null!==e&&!s(e)&&null!==e.constructor&&!s(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)},isFormData:function(e){return"undefined"!=typeof FormData&&e instanceof FormData},isArrayBufferView:function(e){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer},isString:function(e){return"string"==typeof e},isNumber:function(e){return"number"==typeof e},isObject:a,isPlainObject:u,isUndefined:s,isDate:function(e){return"[object Date]"===o.call(e)},isFile:function(e){return"[object File]"===o.call(e)},isBlob:function(e){return"[object Blob]"===o.call(e)},isFunction:c,isStream:function(e){return a(e)&&c(e.pipe)},isURLSearchParams:function(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams},isStandardBrowserEnv:function(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)},forEach:f,merge:function e(){var t={};function n(n,r){u(t[r])&&u(n)?t[r]=e(t[r],n):u(n)?t[r]=e({},n):i(n)?t[r]=n.slice():t[r]=n}for(var r=0,o=arguments.length;r<o;r++)f(arguments[r],n);return t},extend:function(e,t,n){return f(t,(function(t,o){e[o]=n&&"function"==typeof t?r(t,n):t})),e},trim:function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")},stripBOM:function(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e}}},function(e,t,n){"use strict";var r=n(0),o=n(17),i=n(5),s={"Content-Type":"application/x-www-form-urlencoded"};function a(e,t){!r.isUndefined(e)&&r.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}var u,c={transitional:{silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},adapter:(("undefined"!=typeof XMLHttpRequest||"undefined"!=typeof process&&"[object process]"===Object.prototype.toString.call(process))&&(u=n(6)),u),transformRequest:[function(e,t){return o(t,"Accept"),o(t,"Content-Type"),r.isFormData(e)||r.isArrayBuffer(e)||r.isBuffer(e)||r.isStream(e)||r.isFile(e)||r.isBlob(e)?e:r.isArrayBufferView(e)?e.buffer:r.isURLSearchParams(e)?(a(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):r.isObject(e)||t&&"application/json"===t["Content-Type"]?(a(t,"application/json"),function(e,t,n){if(r.isString(e))try{return(t||JSON.parse)(e),r.trim(e)}catch(e){if("SyntaxError"!==e.name)throw e}return(n||JSON.stringify)(e)}(e)):e}],transformResponse:[function(e){var t=this.transitional||c.transitional,n=t&&t.silentJSONParsing,o=t&&t.forcedJSONParsing,s=!n&&"json"===this.responseType;if(s||o&&r.isString(e)&&e.length)try{return JSON.parse(e)}catch(e){if(s){if("SyntaxError"===e.name)throw i(e,this,"E_JSON_PARSE");throw e}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};r.forEach(["delete","get","head"],(function(e){c.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){c.headers[e]=r.merge(s)})),e.exports=c},function(e,t,n){"use strict";function r(e){this.message=e}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,e.exports=r},function(e,t,n){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r<n.length;r++)n[r]=arguments[r];return e.apply(t,n)}}},function(e,t,n){"use strict";var r=n(0);function o(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}e.exports=function(e,t,n){if(!t)return e;var i;if(n)i=n(t);else if(r.isURLSearchParams(t))i=t.toString();else{var s=[];r.forEach(t,(function(e,t){null!=e&&(r.isArray(e)?t+="[]":e=[e],r.forEach(e,(function(e){r.isDate(e)?e=e.toISOString():r.isObject(e)&&(e=JSON.stringify(e)),s.push(o(t)+"="+o(e))})))})),i=s.join("&")}if(i){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+i}return e}},function(e,t,n){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code,status:this.response&&this.response.status?this.response.status:null}},e}},function(e,t,n){"use strict";var r=n(0),o=n(18),i=n(19),s=n(4),a=n(20),u=n(23),c=n(24),f=n(7),l=n(1),p=n(2);e.exports=function(e){return new Promise((function(t,n){var d,h=e.data,m=e.headers,v=e.responseType;function y(){e.cancelToken&&e.cancelToken.unsubscribe(d),e.signal&&e.signal.removeEventListener("abort",d)}r.isFormData(h)&&delete m["Content-Type"];var g=new XMLHttpRequest;if(e.auth){var b=e.auth.username||"",x=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";m.Authorization="Basic "+btoa(b+":"+x)}var w=a(e.baseURL,e.url);function E(){if(g){var r="getAllResponseHeaders"in g?u(g.getAllResponseHeaders()):null,i={data:v&&"text"!==v&&"json"!==v?g.response:g.responseText,status:g.status,statusText:g.statusText,headers:r,config:e,request:g};o((function(e){t(e),y()}),(function(e){n(e),y()}),i),g=null}}if(g.open(e.method.toUpperCase(),s(w,e.params,e.paramsSerializer),!0),g.timeout=e.timeout,"onloadend"in g?g.onloadend=E:g.onreadystatechange=function(){g&&4===g.readyState&&(0!==g.status||g.responseURL&&0===g.responseURL.indexOf("file:"))&&setTimeout(E)},g.onabort=function(){g&&(n(f("Request aborted",e,"ECONNABORTED",g)),g=null)},g.onerror=function(){n(f("Network Error",e,null,g)),g=null},g.ontimeout=function(){var t=e.timeout?"timeout of "+e.timeout+"ms exceeded":"timeout exceeded",r=e.transitional||l.transitional;e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(f(t,e,r.clarifyTimeoutError?"ETIMEDOUT":"ECONNABORTED",g)),g=null},r.isStandardBrowserEnv()){var S=(e.withCredentials||c(w))&&e.xsrfCookieName?i.read(e.xsrfCookieName):void 0;S&&(m[e.xsrfHeaderName]=S)}"setRequestHeader"in g&&r.forEach(m,(function(e,t){void 0===h&&"content-type"===t.toLowerCase()?delete m[t]:g.setRequestHeader(t,e)})),r.isUndefined(e.withCredentials)||(g.withCredentials=!!e.withCredentials),v&&"json"!==v&&(g.responseType=e.responseType),"function"==typeof e.onDownloadProgress&&g.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&g.upload&&g.upload.addEventListener("progress",e.onUploadProgress),(e.cancelToken||e.signal)&&(d=function(e){g&&(n(!e||e&&e.type?new p("canceled"):e),g.abort(),g=null)},e.cancelToken&&e.cancelToken.subscribe(d),e.signal&&(e.signal.aborted?d():e.signal.addEventListener("abort",d))),h||(h=null),g.send(h)}))}},function(e,t,n){"use strict";var r=n(5);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t,n){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},function(e,t,n){"use strict";var r=n(0);e.exports=function(e,t){t=t||{};var n={};function o(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function i(n){return r.isUndefined(t[n])?r.isUndefined(e[n])?void 0:o(void 0,e[n]):o(e[n],t[n])}function s(e){if(!r.isUndefined(t[e]))return o(void 0,t[e])}function a(n){return r.isUndefined(t[n])?r.isUndefined(e[n])?void 0:o(void 0,e[n]):o(void 0,t[n])}function u(n){return n in t?o(e[n],t[n]):n in e?o(void 0,e[n]):void 0}var c={url:s,method:s,data:s,baseURL:a,transformRequest:a,transformResponse:a,paramsSerializer:a,timeout:a,timeoutMessage:a,withCredentials:a,adapter:a,responseType:a,xsrfCookieName:a,xsrfHeaderName:a,onUploadProgress:a,onDownloadProgress:a,decompress:a,maxContentLength:a,maxBodyLength:a,transport:a,httpAgent:a,httpsAgent:a,cancelToken:a,socketPath:a,responseEncoding:a,validateStatus:u};return r.forEach(Object.keys(e).concat(Object.keys(t)),(function(e){var t=c[e]||i,o=t(e);r.isUndefined(o)&&t!==u||(n[e]=o)})),n}},function(e,t){e.exports={version:"0.24.0"}},function(e,t,n){e.exports=n(12)},function(e,t,n){"use strict";var r=n(0),o=n(3),i=n(13),s=n(9);var a=function e(t){var n=new i(t),a=o(i.prototype.request,n);return r.extend(a,i.prototype,n),r.extend(a,n),a.create=function(n){return e(s(t,n))},a}(n(1));a.Axios=i,a.Cancel=n(2),a.CancelToken=n(26),a.isCancel=n(8),a.VERSION=n(10).version,a.all=function(e){return Promise.all(e)},a.spread=n(27),a.isAxiosError=n(28),e.exports=a,e.exports.default=a},function(e,t,n){"use strict";var r=n(0),o=n(4),i=n(14),s=n(15),a=n(9),u=n(25),c=u.validators;function f(e){this.defaults=e,this.interceptors={request:new i,response:new i}}f.prototype.request=function(e){"string"==typeof e?(e=arguments[1]||{}).url=arguments[0]:e=e||{},(e=a(this.defaults,e)).method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=e.transitional;void 0!==t&&u.assertOptions(t,{silentJSONParsing:c.transitional(c.boolean),forcedJSONParsing:c.transitional(c.boolean),clarifyTimeoutError:c.transitional(c.boolean)},!1);var n=[],r=!0;this.interceptors.request.forEach((function(t){"function"==typeof t.runWhen&&!1===t.runWhen(e)||(r=r&&t.synchronous,n.unshift(t.fulfilled,t.rejected))}));var o,i=[];if(this.interceptors.response.forEach((function(e){i.push(e.fulfilled,e.rejected)})),!r){var f=[s,void 0];for(Array.prototype.unshift.apply(f,n),f=f.concat(i),o=Promise.resolve(e);f.length;)o=o.then(f.shift(),f.shift());return o}for(var l=e;n.length;){var p=n.shift(),d=n.shift();try{l=p(l)}catch(e){d(e);break}}try{o=s(l)}catch(e){return Promise.reject(e)}for(;i.length;)o=o.then(i.shift(),i.shift());return o},f.prototype.getUri=function(e){return e=a(this.defaults,e),o(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},r.forEach(["delete","get","head","options"],(function(e){f.prototype[e]=function(t,n){return this.request(a(n||{},{method:e,url:t,data:(n||{}).data}))}})),r.forEach(["post","put","patch"],(function(e){f.prototype[e]=function(t,n,r){return this.request(a(r||{},{method:e,url:t,data:n}))}})),e.exports=f},function(e,t,n){"use strict";var r=n(0);function o(){this.handlers=[]}o.prototype.use=function(e,t,n){return this.handlers.push({fulfilled:e,rejected:t,synchronous:!!n&&n.synchronous,runWhen:n?n.runWhen:null}),this.handlers.length-1},o.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},o.prototype.forEach=function(e){r.forEach(this.handlers,(function(t){null!==t&&e(t)}))},e.exports=o},function(e,t,n){"use strict";var r=n(0),o=n(16),i=n(8),s=n(1),a=n(2);function u(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new a("canceled")}e.exports=function(e){return u(e),e.headers=e.headers||{},e.data=o.call(e,e.data,e.headers,e.transformRequest),e.headers=r.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),r.forEach(["delete","get","head","post","put","patch","common"],(function(t){delete e.headers[t]})),(e.adapter||s.adapter)(e).then((function(t){return u(e),t.data=o.call(e,t.data,t.headers,e.transformResponse),t}),(function(t){return i(t)||(u(e),t&&t.response&&(t.response.data=o.call(e,t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)}))}},function(e,t,n){"use strict";var r=n(0),o=n(1);e.exports=function(e,t,n){var i=this||o;return r.forEach(n,(function(n){e=n.call(i,e,t)})),e}},function(e,t,n){"use strict";var r=n(0);e.exports=function(e,t){r.forEach(e,(function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])}))}},function(e,t,n){"use strict";var r=n(7);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(0);e.exports=r.isStandardBrowserEnv()?{write:function(e,t,n,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(i)&&a.push("domain="+i),!0===s&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},function(e,t,n){"use strict";var r=n(21),o=n(22);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},function(e,t,n){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t,n){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(0),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),(function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;s[t]="set-cookie"===t?(s[t]?s[t]:[]).concat([n]):s[t]?s[t]+", "+n:n}})),s):s}},function(e,t,n){"use strict";var r=n(0);e.exports=r.isStandardBrowserEnv()?function(){var e,t=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(e){var r=e;return t&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return e=o(window.location.href),function(t){var n=r.isString(t)?o(t):t;return n.protocol===e.protocol&&n.host===e.host}}():function(){return!0}},function(e,t,n){"use strict";var r=n(10).version,o={};["object","boolean","number","function","string","symbol"].forEach((function(e,t){o[e]=function(n){return typeof n===e||"a"+(t<1?"n ":" ")+e}}));var i={};o.transitional=function(e,t,n){function o(e,t){return"[Axios v"+r+"] Transitional option '"+e+"'"+t+(n?". "+n:"")}return function(n,r,s){if(!1===e)throw new Error(o(r," has been removed"+(t?" in "+t:"")));return t&&!i[r]&&(i[r]=!0,console.warn(o(r," has been deprecated since v"+t+" and will be removed in the near future"))),!e||e(n,r,s)}},e.exports={assertOptions:function(e,t,n){if("object"!=typeof e)throw new TypeError("options must be an object");for(var r=Object.keys(e),o=r.length;o-- >0;){var i=r[o],s=t[i];if(s){var a=e[i],u=void 0===a||s(a,i,e);if(!0!==u)throw new TypeError("option "+i+" must be "+u)}else if(!0!==n)throw Error("Unknown option "+i)}},validators:o}},function(e,t,n){"use strict";var r=n(2);function o(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var n=this;this.promise.then((function(e){if(n._listeners){var t,r=n._listeners.length;for(t=0;t<r;t++)n._listeners[t](e);n._listeners=null}})),this.promise.then=function(e){var t,r=new Promise((function(e){n.subscribe(e),t=e})).then(e);return r.cancel=function(){n.unsubscribe(t)},r},e((function(e){n.reason||(n.reason=new r(e),t(n.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.prototype.subscribe=function(e){this.reason?e(this.reason):this._listeners?this._listeners.push(e):this._listeners=[e]},o.prototype.unsubscribe=function(e){if(this._listeners){var t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1)}},o.source=function(){var e;return{token:new o((function(t){e=t})),cancel:e}},e.exports=o},function(e,t,n){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}},function(e,t,n){"use strict";e.exports=function(e){return"object"==typeof e&&!0===e.isAxiosError}}])}));
//# sourceMappingURL=axios.min.map
3.2vue.js(太长了,省略)
4.启动工程,访问ajax.html
4.1ajax技术不能处理后端返回的重定向结果
第一次请求调完给发回来一个重定向,又去调用一次
第二次请求返回了一个页面,但是浏览器没有跳转,这里的文本框展示调用接口的返回值
5.配置security返回json数据
5.1未登录时访问受限资源的处理
默认情况会返回一个重定向到登录页面;需要调整成返回json数据
5.2自定义一个AuthenticationEntryPoint接口的实现类,在config包下新建类为NoLoginHandle
package com.hhh.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class NoLoginHandle implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//写处理逻辑,后端给前端返回json数据
//构造json数据
Map<String,String> map=new HashMap<>();
map.put("code","401");
map.put("message","未登录");
//转出为json数据
ObjectMapper mapper=new ObjectMapper();
String json = mapper.writeValueAsString(map);
//设置一个响应头ContentType
httpServletResponse.setContentType("application/json;charset=UTF-8");
//将数据放入response里
httpServletResponse.getWriter().print(json);
}
}
5.3在SecurityConfig类中配置异常处理器
//配置异常处理器,增加
http.exceptionHandling().authenticationEntryPoint(new NoLoginHandle());
5.4启动工程,访问ajaxTest.html,点击请求res1
5.5点击确定后,再次点击请求res1,点击F12 ,点击res1,点击Response
5.6前端接收json数据进行判断
刚刚调用res1接口,返回的是一个重定向,又发起一个login页面的请求,现在直接返回一个已请求的res1,然后返回一个未登录的数据
这时前端会接收到这样一个json数据值就进行判断
6.使用内存配置用户直接访问登录页面
这里在SecurityConfi该类中的内存配置用户的注释放开,将MyUserDetailsService类中配置注释
6.1这时再去ajaxTest页面中请求res1就成功
在login.html登录成功后,cookie保存在浏览器中,又因为两者url在同一个域下(域名一样),再用ajax去发起请求,ajax会带上同源的cookie
6.2如果点击请求res2,会受到权限控制
内存配置用户设置了p1权限,但是在SecurityConfig类中的configure方法下res2需要p2权限才可以访问
6.自定义一个AccessDeniedHandler接口的实现类,在config包下新建类为NoAuthHandler
package com.hhh.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class NoAuthHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
//写处理逻辑,后端给前端返回json数据
//构造json数据
Map<String,String> map=new HashMap<>();
map.put("code","403");
map.put("message","无权限");
//转出为json数据
ObjectMapper mapper=new ObjectMapper();
String json = mapper.writeValueAsString(map);
//设置一个响应头ContentType
httpServletResponse.setContentType("application/json;charset=UTF-8");
//将数据放入response里
httpServletResponse.getWriter().print(json);
}
}
6.4在SecurityConfig类中配置res2没有权限登录后返回json数据
//没有权限就会被accessDeniedHandler()拦截,需要自定义AccessDeniedHandler接口的实现类
http.exceptionHandling()
.authenticationEntryPoint(new NoLoginHandle())
.accessDeniedHandler(new NoAuthHandler());
6.5启动工程,访问res1,res2查看效果
访问ajaxTest.html时均是未登录
访问login.html,登录成功后再次访问res1和res2,res1请求成功,res2无权限