入门,后续更新,供自己学习,记录自己学习,基于Spring boot
码歌视频:https://www.bilibili.com/video/BV1Cz4y1k7rd
Spring Security入门
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
SecurityConfig
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
UserDetailsServiceImpl
package com.kaka.springsecurity.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1、根据用户名去数据库查询,如果不存在抛异常
//不使用数据库
if (!username.equals("admin")){
throw new UsernameNotFoundException("用户不存在");
}
//2、比较密码
String password = passwordEncoder.encode("123");
//user包 org.springframework.security.core.userdetails.User
return new User(username,password, AuthorityUtils
.commaSeparatedStringToAuthorityList("admin,normal"));
}
}
此时就简易入门了
自定义登录页面
Spring Security 有自己的登陆界面,那如何自定义呢?
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login.html");
}
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username"/><br>
密码:<input type="password" name="password"/><br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录成功!!!
</body>
</html>
这样就可以进到login了,但是有一个问题,所有页面都可以进入了
这个时候就需要后面的关于授权的问题了。这里先入门,不深究,即添加一句语句
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//自定义登录界面
.loginPage("/login.html");
//授权
http.authorizeRequests()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
}
这样子写了后,也会出错,重定向次数过多
放行/login.html
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//自定义登录界面
.loginPage("/login.html");
//授权
http.authorizeRequests()
//放行login.html
.antMatchers("/login.html").permitAll()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
}
成功进入
但是这个时候在自己写的lohin界面不可以使用admin 123 进行登录
加上这句话System.out.println(“自定义登录逻辑”);
package com.kaka.springsecurity.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//加上这句话
System.out.println("自定义登录逻辑");
//1、根据用户名去数据库查询,如果不存在抛异常
//不使用数据库
if (!username.equals("admin")){
throw new UsernameNotFoundException("用户不存在");
}
//2、比较密码
String password = passwordEncoder.encode("123");
return new User(username,password, AuthorityUtils
.commaSeparatedStringToAuthorityList("admin,normal"));
}
}
并没有走自定义的方法
加上这个方法.loginProcessingUrl("/login"); 注意接口不可以随便写,这样子才会去执行
http.csrf().disable(); 这句话也需要加上
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//自定义登录界面
.loginPage("/login.html")
//必须和login.html表单提交的接口一样
.loginProcessingUrl("/login")
//成功后的跳转地址 *这个是需要post请求的
.successForwardUrl("/toMain");
//授权
http.authorizeRequests()
//放行login.html
.antMatchers("/login.html").permitAll()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
//这个需要关闭
http.csrf().disable();
}
@Controller
public class LoginController {
@RequestMapping("/toMain")
public String main(){
return "redirect:main.html";
}
}
细节上的问题就解决的差不多了
登录失败页面跳转
这个就是登录失败的样子,有登录成功的url ,就会有失败的
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//自定义登录界面
.loginPage("/login.html")
//必须和login.html表单提交的接口一样
.loginProcessingUrl("/login")
//成功后的跳转地址
.successForwardUrl("/toMain")
//失败后的跳转地址
.failureForwardUrl("/toError");
//授权
http.authorizeRequests()
//放行login.html
.antMatchers("/login.html").permitAll()
//放行error.html 这个也需要放行
.antMatchers("/error.html").permitAll()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
//这个需要关闭
http.csrf().disable();
}
@RequestMapping("/toError")
public String error(){
return "redirect:error.html";
}
设置请求用户名和密码
<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8">
<title>Title</title>
</head><body>
<form action="/login" method="post">
<!-- method="post" name="username" name="password" 必须这样写,否则会失败 -->
用户名:<input type="text" name="username"/><br>
密码:<input type="password" name="password"/><br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
具体原因请研究源码
解决方案
http.formLogin()
//设置用户名和密码参数的名字
.usernameParameter("username")
.passwordParameter("password")
自定义登录成功处理器
现在大部分都是前后端,都是前端自己去跳
但是.successForwardUrl(“https://www.baidu.com/”)是不可行的
需要自定义MyAuthenticationSuccessHandler
并通过这个.successHandler(new MyAuthenticationSuccessHandler(“https://www.baidu.com/”))配置,前面那个必须注释掉
package com.kaka.springsecurity.handler;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
User user = (User) authentication.getPrincipal();
System.out.println(user.getUsername());
System.out.println(user.getPassword());
System.out.println(user.getAuthorities());
httpServletResponse.sendRedirect(url);
}
}
http.formLogin()
//设置用户名和密码参数的名字
.usernameParameter("username")
.passwordParameter("password")
//自定义登录界面
.loginPage("/login.html")
//必须和login.html表单提交的接口一样
.loginProcessingUrl("/login")
// //成功后的跳转地址
// .successForwardUrl("/toMain")
//成功后跳转到百度
.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
//失败后的跳转地址
.failureForwardUrl("/toError");
这样就是跳转登陆界面了,下面看一下基本的信息
自定义登录逻辑adminnull[admin, normal]
这就是输出的基本信息
自定义登录失败处理器
和成功一样,写一个MyAuthenticationFailureHandler
package com.kaka.springsecurity.handler;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
public MyAuthenticationFailureHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
e.printStackTrace();
httpServletResponse.sendRedirect(url);
}
}
http.formLogin()
//设置用户名和密码参数的名字
.usernameParameter("username")
.passwordParameter("password")
//自定义登录界面
.loginPage("/login.html")
//必须和login.html表单提交的接口一样
.loginProcessingUrl("/login")
// //成功后的跳转地址
// .successForwardUrl("/toMain")
//成功后跳转到百度
//.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
.successHandler(new MyAuthenticationSuccessHandler("/main.html"))
// //失败后的跳转地址
// .failureForwardUrl("/toError");
.failureHandler(new MyAuthenticationFailureHandler("/error.html"));
登录失败,运行结果
自定义登录逻辑 org.springframework.security.authentication.BadCredentialsException: 用户名或密码错误
现在前后端分离,更常用failureHandler,而不是failureForwardUrl
授权(关于anyRequest)
有一个基本的细节问题,anyRequest必须放在后面
//授权http.authorizeRequests()
//放行login.html
.antMatchers("/login.html").permitAll()
//放行error.html
.antMatchers("/error.html").permitAll()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
如果放在前面,即错误的写法
//授权
http.authorizeRequests()
.anyRequest().authenticated()
//放行login.html
.antMatchers("/login.html").permitAll()
//放行error.html
.antMatchers("/error.html").permitAll()
//所有请求都必须认证才能访问,必须登录
;
程序直接报错
Caused by: java.lang.IllegalStateException: Can't configure antMatchers after anyRequest
关于antMatchers
- 这个是最常用的方法(还有两个重载)
public C antMatchers(String... antPatterns)
?
:匹配一个字符*
:匹配一个或多个字符**
:匹配一个或多个目录
打个比方
//表示所有的css,js下的,都放行
.antMatchers("/css/**,/js/**").permitAll();
//放行所有的png,不仅仅是在images文件夹下的png
.antMatchers("/**/*.png").permitAll();
关于regexMatchers
基本使用
//授权
http.authorizeRequests()
//放行login.html
.antMatchers("/login.html").permitAll()
//放行error.html
.antMatchers("/error.html").permitAll()
// //放行所有的png,不仅仅是在images文件夹下的png
// .antMatchers("/**/*.png").permitAll()
//正则表达式
.regexMatchers(".+[.]png").permitAll()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
如果完全匹配请求呢?
@GetMapping("/demo")
public String demo(){return "demo";}
.regexMatchers("/demo").permitAll()
这样子是可以直接访问的
.regexMatchers(HttpMethod.POST,"/demo").permitAll()
我们的controller是GetMapping,就也会屏蔽
关于mvcMatchers
基本使用
//mvc匹配.mvcMatchers("/demo").permitAll()
添加前缀
spring.mvc.servlet.path=/kaka
- 第一种
.regexMatchers("/kaka/demo").permitAll()
- 第二种
.mvcMatchers("/demo").servletPath("/kaka").permitAll()
- 第三种
.antMatchers("/kaka/demo").permitAll()
内置权限控制
这是源代码里面写的
static final String permitAll = "permitAll";//表示所匹配的 URL 任何人都允许访问
private static final String denyAll = "denyAll";//表示所匹配的 URL 都不允许被访问
private static final String anonymous = "anonymous";//表示可以匿名访问匹配的 URL
private static final String authenticated = "authenticated";//表示所匹配的 URL 都需要被认证才能访问
private static final String fullyAuthenticated = "fullyAuthenticated";//如果用户不是被 remember me 的,才可以访问
private static final String rememberMe = "rememberMe";//被“remember me”的用户允许访问
用户角色权限说明
画的有点丑
一般来说,用户角色权限是有五张表的,这篇文章是入门,并且也没有使用数据库,所以就不进行细分了
基于权限判断
将前面代码进行适量调整,并添加main1.html
package com.kaka.springsecurity.config;
import com.kaka.springsecurity.handler.MyAuthenticationFailureHandler;
import com.kaka.springsecurity.handler.MyAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//设置用户名和密码参数的名字
.usernameParameter("username")
.passwordParameter("password")
//自定义登录界面
.loginPage("/login.html")
//必须和login.html表单提交的接口一样
.loginProcessingUrl("/login")
//成功后的跳转地址
.successForwardUrl("/toMain")
//成功后跳转到百度
//.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
// .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
// //失败后的跳转地址
.failureForwardUrl("/toError");
// .failureHandler(new MyAuthenticationFailureHandler("/error.html"));
//授权
http.authorizeRequests()
//放行login.html
.antMatchers("/login.html").permitAll()
//放行error.html
.antMatchers("/error.html").permitAll()
// //正则表达式
// .regexMatchers(HttpMethod.POST,"/demo").permitAll()
// .regexMatchers("/kaka/demo").permitAll()
//mvc匹配
// .mvcMatchers("/demo").servletPath("/kaka").permitAll()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
//这个需要关闭
http.csrf().disable();
}
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录成功!!!<a href="main1.html">跳转</a>
</body>
</html>
main1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
admin权限控制
</body>
</html>
前面的UserDetailsServiceImpl是给了两个权限
return new User(username,password, AuthorityUtils
.commaSeparatedStringToAuthorityList("admin,normal"));
设置权限 严格区分大小写,成功就会进入,失败会报403错误
//单个权限
.antMatchers("/main1.html").hasAuthority("admin")
//多个权限 虽然admiN是错误的,但是有admin,依旧可以进入
.antMatchers("/main1.html").hasAnyAuthority("admin","admiN")
基于角色判断
在前面的UserDetailsServiceImpl 加上角色,必须有ROLE_前缀
return new User(username,password, AuthorityUtils
.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));
这里不要加前缀,因为他会自动添加,这里也严格区分大小写,这里错误也是403
//单个
.antMatchers("/main1.html").hasRole("abc")
//多个
.antMatchers("/main1.html").hasAnyRole("abc","Abc")
基于IP判断
获取ip地址的方法
request.getRemoteAddr();
有一个问题,localhost获取的是0.0.0.0.0.0.0.1,127.0.0.1获取的是127.0.0.1,还有自己的ip地址,他是不同的
.antMatchers("/main1.html").hasIpAddress("127.0.0.1")
自定义403页面
创建MyAccessDeniedHandler
package com.kaka.springsecurity.handler;
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.io.PrintWriter;
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
//响应状态
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
//返回json格式
httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员\"}");
writer.flush();
writer.close();
}
}
因为留了一个之前的.antMatchers("/main1.html").hasRole(“abC”),他会403错误
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//设置用户名和密码参数的名字
.usernameParameter("username")
.passwordParameter("password")
//自定义登录界面
.loginPage("/login.html")
//必须和login.html表单提交的接口一样
.loginProcessingUrl("/login")
//成功后的跳转地址
.successForwardUrl("/toMain")
//成功后跳转到百度
//.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
// .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
// //失败后的跳转地址
.failureForwardUrl("/toError");
// .failureHandler(new MyAuthenticationFailureHandler("/error.html"));
//授权
http.authorizeRequests()
//放行login.html
.antMatchers("/login.html").permitAll()
//放行error.html
.antMatchers("/error.html").permitAll()
// .antMatchers("/main1.html").hasAuthority("admin")
.antMatchers("/main1.html").hasRole("abC")
// //正则表达式
// .regexMatchers(HttpMethod.POST,"/demo").permitAll()
// .regexMatchers("/kaka/demo").permitAll()
//mvc匹配
// .mvcMatchers("/demo").servletPath("/kaka").permitAll()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
//异常处理
http.exceptionHandling()
.accessDeniedHandler(new MyAccessDeniedHandler());
//这个需要关闭
http.csrf().disable();
}
报错
基于access的访问控制
之前说了很多访问控制,有内置的,角色,权限等等,但是他们都是基于access的
随便复制了几个,可以发现,都是调用的accsee的
public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry permitAll() {
return this.access("permitAll");
}
public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry anonymous() {
return this.access("anonymous");
}
public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry rememberMe() {
return this.access("rememberMe");
}
使用说明
.antMatchers("/login.html").access("permitAll");
.antMatchers("/login.html").access("hasRole('abc')");
自定义access方法
package com.kaka.springsecurity.service;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
@Service
public class MyServiceImpl {
public boolean hasPermission(HttpServletRequest request, Authentication authentication){
//获取主体
Object principal = authentication.getPrincipal();
//判断主体是否属于UserDetails
if (principal instanceof UserDetails){
//获取权限
UserDetails userDetails = (UserDetails) principal;
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
//判断请求的URI是否有权限 所以权限需要加上URI 否则会报500错
return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
}
return false;
}
}
//所有请求都必须认证才能访问,必须登录
//.anyRequest().authenticated();
//自定义access方法
.anyRequest().access("@myServiceImpl.hasPermission(httpServletRequest,authentication)");
return new User(username,password, AuthorityUtils
.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc,/main.html"));
基于注解的访问控制
@SpringBootApplication
//开启注解配置 注意一定要开启
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SpringsecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringsecurityApplication.class, args);
}
}
这些注解可以放在service上,也可以是controller或者是controller的方法上,但是放在方法上好控制,所以一般放在方法上
前面的代码就稍微调整一下 不改会报500错
@Secured
是专门判断是否具有该角色的,
他会先登录,在进行判断角色,就一开始写了自定义登录逻辑,他就会输出在控制台
@Secured("ROLE_abc")
@RequestMapping("/toMain"
public String main(){
return "redirect:main.html";
}
@PreAuthorize
执行前判断是否有权限,PostAuthorize
执行后判断是否有权限,很少用
这两个都可以,配置类不可以有ROLE_,但是他们都严格要求大小写
@PreAuthorize("hasRole('abc')")
//@PreAuthorize("hasRole('ROLE_abc')")
@RequestMapping("/toMain")
public String main(){
return "redirect:main.html";
}
RememberMe功能实现
ReRememberMe功能实现依赖Spring-jdbc,所以导入mybatis和数据库依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置配置文件
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=UTF8&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
数据库建表
package com.kaka.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
@Configuration
public class RememberMeConfig {
@Autowired(required = true)
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//设置数据源
jdbcTokenRepository.setDataSource(dataSource);
//自动建表,第一次使用开启,后面注释掉,
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
package com.kaka.springsecurity.config;
import com.kaka.springsecurity.handler.MyAccessDeniedHandler;
import com.kaka.springsecurity.handler.MyAuthenticationFailureHandler;
import com.kaka.springsecurity.handler.MyAuthenticationSuccessHandler;
import com.kaka.springsecurity.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PersistentTokenRepository persistentTokenRepository;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//设置用户名和密码参数的名字
.usernameParameter("username")
.passwordParameter("password")
//自定义登录界面
.loginPage("/login.html")
//必须和login.html表单提交的接口一样
.loginProcessingUrl("/login")
//成功后的跳转地址
.successForwardUrl("/toMain")
//成功后跳转到百度
//.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
// .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
// //失败后的跳转地址
.failureForwardUrl("/toError");
// .failureHandler(new MyAuthenticationFailureHandler("/error.html"));
//授权
http.authorizeRequests()
//放行login.html
.antMatchers("/login.html").permitAll()
//放行error.html
.antMatchers("/error.html").permitAll()
// .antMatchers("/main1.html").hasAuthority("admin")
// .antMatchers("/main1.html").hasRole("abC")
// //正则表达式
// .regexMatchers(HttpMethod.POST,"/demo").permitAll()
// .regexMatchers("/kaka/demo").permitAll()
//mvc匹配
// .mvcMatchers("/demo").servletPath("/kaka").permitAll()
//所有请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
// //自定义access方法
// .anyRequest().access("@myServiceImpl.hasPermission(httpServletRequest,authentication)");
//异常处理
http.exceptionHandling()
.accessDeniedHandler(new MyAccessDeniedHandler());
//记住我
http.rememberMe()
.tokenRepository(persistentTokenRepository)
//超时时间
.tokenValiditySeconds(60)
.userDetailsService(userDetailsService);
//这个需要关闭
http.csrf().disable();
}
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
登录一次之后,自动生成了表
第二次登录,每次都会去增加
使用thmeleaf
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
命名空间
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
写一个themeleaf页面
<!DOCTYPE html>
<html lang="en" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录账号:<span sec:authentication="name"></span><br>
登录账号:<span sec:authentication="principal.username"></span><br>
凭证:<span sec:authentication="credentials"></span><br>
权限和角色:<span sec:authentication="authorities"></span><br>
客户端地址:<span sec:authentication="details.remoteAddress"></span><br>
sessionId:<span sec:authentication="details.sessionId"></span><br>
<button sec:authorize="hasAnyAuthority('/insert')">新增</button>
<button sec:authorize="hasAnyAuthority('/delete')">删除</button>
<button sec:authorize="hasAnyAuthority('/update')">修改</button>
<button sec:authorize="hasAnyAuthority('/select')">查看</button>
<br/>
通过角色判断:
<button sec:authorize="hasRole('abc')">新增</button>
<button sec:authorize="hasRole('abc')">删除</button>
<button sec:authorize="hasRole('abc')">修改</button>
<button sec:authorize="hasRole('abc')">查看</button>
</body>
</html>
根据权限展示
return new User(username,password, AuthorityUtils
.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc,/insert,/delete"));
结果
退出
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录成功!!!<a href="main1.html">跳转</a><br>
<a href="/logout">退出</a>
</body>
</html>
就可以直接退出了,但是有一些问题
第一个问题http://localhost:8080/login.html?logout 这是它的链接,很明显,我们想要的是重定向会登录界面,而不是传参logout
这样子就可以了
//退出
http.logout()
.logoutSuccessUrl("/login.html");
第二种
//退出
http.logout()
.logoutUrl("/user/logout");
<a href="/user/logout">退出</a>
这样子也行
logout 也可以自定义handler,但是入门级暂时不研究这么深
csrf
CSRF
全拼为Cross Site Request Forgery
,译为跨站请求伪造。CSRF
指攻击者盗用了你的身份,以你的名义发送恶意请求。- 包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…
- 造成的问题:个人隐私泄露以及财产安全。
Spring Security 是默认开启crsf的,之前我们是关闭了的
把login.html 放到templates里面,把配置里面的路径改一下,注释关闭crsf的代码
@GetMapping("/showLogin")
public String login(){
return "login";
}
结果
// http://localhost:8080/login{
"status": "error",
"msg": "权限不足,请联系管理员"
}
修改一下登陆界面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
<input type="hidden" name="_csrf" th:value="${_csrf.token}" th:if="${_csrf}"/>
用户名:<input type="text" name="username"/><br/>xxx
密码:<input type="password" name="password"/><br/>
记住我:<input type="checkbox" name="remember-me" value="true"/><br/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
查看请求头
_csrf: 5fbacf62-6939-40c1-ae38-185b9f63756cusername: adminpassword: 123