概述
Spring security使用
- Spring security
- 一、SpringSecurity简介
- 二、创建SpringSecurity项目(与SpringBoot整合)
- 三、UserDetailsService详解
- 1、方法的介绍
- 2、返回值
- 3、方法参数
- 4、异常
- 四、PasswordEncoder密码解析器详解
- 1、PasswordEncoder接口介绍
- 2、内置解析器介绍
- 3、BCryptPasswordEncoder简介
- 五、自定义登陆页面
- 1、编写登陆页面
- 2、修改配置类
- 3、编写控制器代码
- 六、认证过程其他配置
- 1、失败跳转
- 1、1:编写失败页面
- 1、2:修改配置类
- 1、3:添加控制类方法
- 1、4:设置fail.html不需要认证
- 2、设置请求账户和密码的参数名
- 七、访问控制url匹配
- url匹配规则.权限控制方法
- 1、anyRequest()
- 2、antMatcher()
- 3、regexMatchers()
- 八、内置访问控制方法介绍(常用的)
- 1、permitAll()
- 2、authenticated()
- 3、anonymous()
- 4、denyAll()
- 5、rememberMe()
- 6、fullyAuthenticated()
- 九、角色权限判断
- 1、hasAuthority(String)
- 2、hasAnyAuthority(String...)
- 3、hasRole(String)
- 4、hasAnyRole(String...)
- 5、hasIpAddress(String)
- 十、自定义403处理方案
- 解决方式一
- 1、1:新建类实现AccessDeniedHandler
- 1、2:修改配置文件
- 解决方式二
- 2、1:创建一个forbidden.html页面
- 2、2:修改配置页面
- 十一、基于表达式的控制访问
- 1、access()的使用
- 2、使用自定义权限逻辑方法
- 2、1:新建接口及实现类
- 十二、基于注解的访问控制
- 1、@Secured
- 1、1:实现步骤
- 1、1、1:开启注解
- 1、1、2:在控制层方法上添加@Secured
- 1、1、3:配置类保留最基本配置即可
- 2、@PreAuthorize/@PostAuthorize
- 2、1:实现步骤
- 2、1、1:开启注解
- 2、1、2:添加注解到控制层方法上
- 2、1、3:与@Secured一样即可
- 十三、Remember Me功能实现
- 1、编写RememberMe配置文件
- 2、修改SpringSecurityConfig配置文件
- 3、在客户端中添加复选框
- 4、设置remember me的过去时间
- 十四、Thymeleaf中SpringSecurity的使用
- 1、获取属性
- 1、1:实现步骤
- 1、1、1:新建index.html
- 1、1、2:编写控制器
- 2、 权限判断
- 2、1:不同权限的用户显示不同的按钮
- 2、1、1:设置用户角色和权限
- 2、1、2:控制页面显示效果
- 十五、退出登陆
- 十六、SpringSecurity中CSRF
- 1、什么是CSRF
- 2、Spring Security中的CSRF
- 2、1:实现步骤
- 2、1、1:编写控制方法
- 2、1、2:修改login.html页面
- 2、1、3:修改配置类
- 2、2:开启CSRF后的注销
Spring security
一、SpringSecurity简介
Spring Security是一个高度自定义的安全框架。利用Spring IoC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。
二、创建SpringSecurity项目(与SpringBoot整合)
SpringSecurity已经被SpringBoot集成,只需要引入启动器即可,如下所示:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<!--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>
</dependencies>
引入启动器后,SpringSecurity就已经生效了,默认拦截所有请求,如果用户没有登录,访问http://localhost:8080/会跳转到内置的登陆页面,如下所示:
默认的登陆用户名是user,密码是控制台输出的密码:
然后,我们在浏览器中输入账号和密码后会显示404错误内容,我们认证通过但是,并没有找到控制器,所以给出404错误。
三、UserDetailsService详解
在实际应用中,用户名和密码是从数据库中查询出来的,UserDetailsService接口可以让我们自定义认证逻辑(实现UserDetailsService接口,然后将实现类交由spring管理即可),从而不需要框架自定义的username和密码。
1、方法的介绍
方法的作用:根据用户名加载用户
2、返回值
返回值是UserDetails,是一个接口,定义如下:
此接口的实现类有:
我们只需要其中的User类,User类的全限定名为:
org.springframework.security.core.userdetails.User
注意:不要与自己定义的User类搞混了
3、方法参数
方法参数表示用户名。此值是客户端表单传递过来的数据。默认情况下表单name值必须叫username,否则无法接收。
4、异常
UsernameNotFoundException 用户名没有发现异常。在loadUserByUsername中是需要通过自己的逻辑从数据库中取值的。如果通过用户名没有查询到对应的数据,应该抛出UsernameNotFoundException,系统就知道用户名没有查询到。
代码如下(这里添加了从数据库根据用户名查询用户的这一步骤,自行添加相关依赖(mybatis、mysql),创建model,mapper):
@Component
public class MyAuth implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("客户端输入的username:"+username);
//连接数据库操作,进行校验
com.java.pojo.User user = userMapper.selectUserByUsername(username);
if(user == null){
throw new UsernameNotFoundException("用户名不存在!!!");
}
/**
* security的user对象的参数(String username, String password,
* Collection<? extends GrantedAuthority> authorities)解释:
* username:用户名
* password:密码
* authorities:是一个集合,表示授权的集合,参数不能为null,为null会抛异常
* ( 否则抛异常 org.springframework.security.authentication.InternalAuthenticationServiceException: Cannot pass a null GrantedAuthority collection)
* 我们通过权限工具类将权限字符串生成一个authorities集合即可
*/
return new User(user.getUsername(),user.getPwd(), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,guest,admin"));
}
}
项目启动报错:没有密码解析器,所以我们需要创建一个密码解析器
四、PasswordEncoder密码解析器详解
我们有了用户名还不行,一个完整的登陆信息还需要包含密码,我们自定义了用户名还需要自定义密码解析器,不然SpringSecurity无法解析我们前台输入的明文密码(SpringSecurity有多个内置的密码解析器,SpringSecurity所展示出来的密码都是单向加密后的密文)
1、PasswordEncoder接口介绍
encode():把参数按照特定的解析规则进行解析。
matches():验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。
upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回true,否则返回false。默认返回false。
2、内置解析器介绍
3、BCryptPasswordEncoder简介
BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时多使用这个解析器。
BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认10.
在这里我们只需要将BCryptPasswordEncoder注入到spring的容器中即可,创建配置类,配置类中有个方法返回BCryptPasswordEncoder对象,方法上添加@Bean注解,代码如下:
现在我们启动项目就不会报错,我们在浏览器中输入账号和密码后会显示404错误内容,我们认证通过但是,并没有找到控制器,所以给出404错误。
五、自定义登陆页面
虽然Spring Security给我们提供了登录页面,但是对于实际项目中,大多喜欢使用自己的登录页面。所以Spring Security中不仅仅提供了登录页面,还支持用户自定义登录页面。实现过程也比较简单,只需要修改配置类即可。
1、编写登陆页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<!--没有对应的控制器(action)也可以-->
<form method="post">
用户名:<p><input type="text" name="username"/></p>
密码:<p><input type="password" name="password"/></p>
<p><input type="submit" value="登录"/></p>
</form>
</body>
</html>
2、修改配置类
继承WebSecurityConfigurerAdapter抽象类,实现抽象方法configure(这个方法主要是用于设置页面、设置认证逻辑以及权限判断用的),代码如下:
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 路径认证控制器
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
/**
* formLogin(): 表示手动配置认证逻辑
* loginProcessingUrl() : 设置登陆页面的表单提交路径,可以不真实存在
* successForwardUrl() : 设置登陆成功后的请求路径
* loginPage() : 设置登陆页面
*/
http.formLogin()
.loginProcessingUrl("/login")
.successForwardUrl("/main")
.loginPage("/login.html");
//authorizeRequests() : 设置请求权限
http.authorizeRequests()
.antMatchers("/login.html").permitAll()
.anyRequest().authenticated();
/**
* 关闭csrf保护
*/
http.csrf().disable();
}
/**
* 密码凭证器
*
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3、编写控制器代码
@RequestMapping(value = {"/","/main"})
@ResponseBody
public String index(){
return "ok";
}
六、认证过程其他配置
1、失败跳转
1、1:编写失败页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
操作失败,请重新登录. <a href="/login.html">跳转</a>
</body>
</html>
1、2:修改配置类
http.formLogin()
.loginProcessingUrl("/login")
.failureForwardUrl("/fail") //添加跳转失败路径
.successForwardUrl("/main")
.loginPage("/login.html");
1、3:添加控制类方法
@RequestMapping("/fail")
public String toLogin(){
return "redirect:/fail.html";
}
1、4:设置fail.html不需要认证
.antMatchers("/login.html", "/fail.html").permitAll() //将/fail.html添加进去即可
2、设置请求账户和密码的参数名
当进行登录时会执行UsernamePasswordAuthenticationFilter过滤器。
UsernamePasswordAuthenticationFilter登录认证部分源码如下:
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
//SpringSecurity内部定义的登录表单input的name属性的值,分别是username、password
//登录提交的数据的名称必须与之对应
//如果登录表单提交时,input的name属性的值不为username、password,则SpringSecurity
//无法接受表单提交的数据
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
//postOnly=true:默认情况下只允许POST请求。
private boolean postOnly = true;
//当前方法给定了登录认证时action的值,以及表单的提交方式
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
//当前方法就是登录认证的核心方法
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
//判断表单提交的方式是否为post,不是则抛出异常
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
//obtainUsername():底层就是通过request.getParameter(usernameParameter)获取的
// usernameParameter就是当前类定义的常量
String username = obtainUsername(request);
//与上面的username一样
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//这个就是认证的具体细节了,有兴趣的可以自己去看看
return this.getAuthenticationManager().authenticate(authRequest);
}
修改页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login" method="post">
用户名:<p><input type="text" name="name"/></p>
密码:<p><input type="password" name="pwd"/></p>
<p><input type="submit" value="登录"/></p>
</form>
</body>
</html>
修改配置类
http.formLogin()
.loginProcessingUrl("/login")
.passwordParameter("pwd")
.usernameParameter("name")
.failureForwardUrl("/fail")
.successForwardUrl("/main")
.loginPage("/login.html");
七、访问控制url匹配
上面只是简单的使用了下请求权限的用法,下面我们将对请求权限简单的介绍一下。
在配置类中http.authorizeRequests()主要是对url进行控制,也就是我们所说的授权(访问控制)。http.authorizeRequests()也支持连缀写法,总体公式为:
url匹配规则.权限控制方法
通过上面的公式可以有很多url匹配规则和很多权限控制方法。这些内容进行各种组合就形成了Spring Security中的授权。
在所有匹配规则中取所有规则的交集。配置顺序影响了之后授权效果,越是具体的应该放在前面,越是笼统的应该放到后面。
1、anyRequest()
在之前认证过程中我们就已经使用过anyRequest(),表示匹配所有的请求。
2、antMatcher()
参数是不定向参数,每个参数是一个ant表达式,用于匹配URL规则。
规则如下:
? 匹配一个字符
* 匹配0个或多个字符
** 匹配0个或多个目录
在实际项目中经常需要放行所有静态资源,下面演示放行js文件夹下所有脚本文件。
.antMatchers("/js/**").permitAll()
3、regexMatchers()
使用正则表达式进行匹配。和antMatchers()主要的区别就是参数,antMatchers()参数是ant表达式,regexMatchers()参数是正则表达式。
演示所有以.js结尾的文件都被放行。
.regexMatchers(**".+[.]js"**).permitAll()
无论是antMatchers()还是regexMatchers()都具有两个参数的方法,其中第一个参数都是HttpMethod,表示请求方式,当设置了HttpMethod后表示只有设定的特定的请求方式才执行对应的权限设置。
枚举类型HttpMethod内置属性如下:
八、内置访问控制方法介绍(常用的)
Spring Security匹配了URL后调用了permitAll()表示不需要认证,随意访问。在Spring Security中提供了多种内置控制。
1、permitAll()
permitAll()表示所匹配的URL任何人都允许访问。
2、authenticated()
authenticated()表示所匹配的URL都需要被认证才能访问。
3、anonymous()
anonymous()表示可以匿名访问匹配的URL。和permitAll()效果类似,只是设置为anonymous()的url会执行filter 链中。认证后就无法访问anonymous()修饰的页面
4、denyAll()
denyAll()表示所匹配的URL都不允许被访问。
5、rememberMe()
被“remember me”的用户允许访问
6、fullyAuthenticated()
如果用户不是被remember me的,才可以访问。
九、角色权限判断
除了之前讲解的内置权限控制。Spring Security中还支持很多其他权限控制。这些方法一般都用于用户已经被认证后,判断用户是否具有特定的要求。
1、hasAuthority(String)
判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建User对象时指定的。
在配置类中通过hasAuthority(“/user”)设置具有“/user”权限时才能访问。
.antMatchers("/user.html").hasAuthority("/user")
2、hasAnyAuthority(String…)
如果用户具备给定权限中某一个,就允许访问。注意,是区分大小写的,下面代码中由于认证具有“/product”与“/order”,所以用户可以访问/product.html
.antMatchers("/product.html").hasAnyAuthority("/product", "/order")
3、hasRole(String)
如果用户具备给定角色就允许访问。否则出现403。
参数取值来源于自定义登录逻辑UserDetailsService实现类中创建User对象时给User赋予的授权。
在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称。例如:ROLE_abc 其中abc是角色名,ROLE_是固定的字符开头。使用hasRole()时参数也只写abc即可。否则启动报错。
.antMatchers("/system.html").hasRole("管理员")
4、hasAnyRole(String…)
如果用户具备给定角色的任意一个,就允许被访问
5、hasIpAddress(String)
如果请求是指定的IP就运行访问。
可以通过request.getRemoteAddr()获取ip地址。
需要注意的是在本机进行测试时localhost和127.0.0.1输出的ip地址是不一样的。
当浏览器中通过localhost进行访问时控制台打印的内容:
当浏览器中通过127.0.0.1访问时控制台打印的内容:
当浏览器中通过具体ip进行访问时控制台打印内容:
十、自定义403处理方案
使用Spring Security时经常会看见403(无权限),默认情况下显示的效果如下:
而在实际项目中可能都是一个异步请求,显示上述效果对于用户就不是特别友好了。Spring Security支持自定义权限受限。
解决方式一
1、1:新建类实现AccessDeniedHandler
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{"status":"error","msg":"权限不足,请联系管理员!"}");
out.flush();
out.close();
}
}
1、2:修改配置文件
配置类中重点添加异常处理器。设置访问受限后交给哪个对象进行处理。
myAccessDeniedHandler是在配置类中进行自动注入的。
//异常处理 http.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler);
解决方式二
2、1:创建一个forbidden.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>权限不足,请联系管理员</h2>
</body>
</html>
2、2:修改配置页面
/** * 异常处理方式 */
http.exceptionHandling()
.accessDeniedPage("/forbidden.html");
十一、基于表达式的控制访问
1、access()的使用
之前学习的登陆用户权限判断实际上底层都是调用access(表达式),如permitAll()、anonymous()等。
public ExpressionInterceptUrlRegistry permitAll() {
return access(permitAll);
}
public ExpressionInterceptUrlRegistry anonymous() {
return access(anonymous);
}
那这个access(表达式)究竟是什么呢?
这是官方原文(原文地址:https://docs.spring.io/spring-security/site/docs/5.4.1/reference/html5/#authz-hierarchical-roles)
借助翻译工具翻译后,大概意思是:Spring Security 3.0引入了使用Spring EL表达式作为一种授权机制的能力,除了简单地使用以前见过的配置属性和访问决策投票人之外。基于表达式的访问控制建立在相同的体系结构上,但允许将复杂的布尔逻辑封装在单个表达式中。
表达式格式为:
也就是说我们可以基于access(表达式)的方式完成一些较为复杂的权限判断,我们以hasRole()和permitAll()为例:
2、使用自定义权限逻辑方法
虽然这里面已经包含了很多的表达式(方法)但是在实际项目中很有可能出现需要自己自定义逻辑的情况。比如:判断登录用户是否具有访问当前URL权限。
2、1:新建接口及实现类
自定义一个接口,接口中的方法名必须是hasPermission,否则Security无法识别,代码如下:
public interface MyService {
/**
* 判断当前登录用户是否具有访问当前url权限
* @param request request对象
* @param authentication 权限对象
* @return
*/
boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
实现类代码如下:
@Service
public class MyServiceImpl implements MyService {
//截取字符串的开始位置
private final Integer SUBSTRING_REQUEST_URI = 1;
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
//我们只需要从request中获取uri即可
String requestURI = request.getRequestURI();
//字符串截取,不需要前面的/,所以是从1开始
requestURI = requestURI.substring(SUBSTRING_REQUEST_URI,requestURI.length());
// 获取当前登录的用户,判断是否为UserDetails的实现类
Object principal = authentication.getPrincipal();
if(principal instanceof UserDetails){
User user = (User) principal;
//从user对象中获取权限集合
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return authorities.contains(requestURI);
}
return false;
}
}
修改配置类
/**
* 自定义方法实现访问控制
*/
.antMatchers("/product").access("@myServiceImpl.hasPermission(request,authentication)")
十二、基于注解的访问控制
在Spring Security中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过@EnableGlobalMethodSecurity进行开启后使用。
如果设置的条件允许,程序正常执行。如果不允许会报500
这些注解可以写到Service接口或方法上上也可以写到Controller或Controller的方法上。通常情况下都是写在控制器方法上的,控制接口URL是否允许被访问。
1、@Secured
@Secured是专门用于判断是否具有角色的。能写在方法或类上。@Secured参数要以ROLE_开头。
1、1:实现步骤
1、1、1:开启注解
在启动类上添加@EnableGlobalMethodSecurity(securedEnabled = true)
1、1、2:在控制层方法上添加@Secured
1、1、3:配置类保留最基本配置即可
protected void configure(HttpSecurity http) throws Exception {
// 表单认证
http.formLogin()
.loginProcessingUrl("/login") //当发现/login时认为是登录,需要执行UserDetailsServiceImpl
.successForwardUrl("/main") //此处是post请求
.loginPage("/login.html");
// url 拦截
http.authorizeRequests()
.antMatchers("/login.html","/noauth.html").permitAll() //login.html不需要被认证
.anyRequest().authenticated();//所有的请求都必须被认证。必须登录后才能访问。
//关闭csrf防护
http.csrf().disable();
}
2、@PreAuthorize/@PostAuthorize
@PreAuthorize和@PostAuthorize都是方法或类级别注解。
@PreAuthorize表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解,注解的参数和access()方法参数取值相同,都是权限表达式。
@PostAuthorize表示方法或类执行结束后判断权限,此注解很少被使用到。
2、1:实现步骤
2、1、1:开启注解
2、1、2:添加注解到控制层方法上
2、1、3:与@Secured一样即可
十三、Remember Me功能实现
Spring Security 中Remember Me为“记住我”功能,用户只需要在登录时添加remember-me复选框,取值为true。Spring Security会自动把用户信息存储到数据源中,以后就可以不登录进行访问。
在上面我们已经添加了数据库的依赖以及配置连接信息,我们只需要做下面几步操作即可实现Remember Me功能
1、编写RememberMe配置文件
@Configuration
public class RememberMeConfig {
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository getTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);//将token信息保存到数据库中
return jdbcTokenRepository;
}
}
2、修改SpringSecurityConfig配置文件
在SecurityConfig中添加RememberMeConfig和UserDetailsService实现类对象,并自动注入。
在configure中添加下面配置内容。
/**
* rememberMe处理方式
*/
http.rememberMe()
.userDetailsService(myAuth) //自己编写的登陆认证类
.tokenRepository(persistentTokenRepository);
3、在客户端中添加复选框
在客户端登录页面中添加remember-me的复选框,只要用户勾选了复选框下次就不需要进行登录了。
<form action="/login" method="post">
用户名:<p><input type="text" name="username"/></p>
密码:<p><input type="password" name="pwd"/></p>
<input type="checkbox" name="remember-me" value="true"/> <br/>
<p>记住我:<input type="submit" value="登录"/></p>
</form>
4、设置remember me的过去时间
默认2周时间。但是可以通过设置状态有效时间,即使项目重新启动下次也可以正常登录。
/**
* rememberMe处理方式
*/
http.rememberMe()
.tokenValiditySeconds(120)//单位:秒
.userDetailsService(myAuth) //自己编写的登陆认证类
.tokenRepository(persistentTokenRepository);
十四、Thymeleaf中SpringSecurity的使用
Spring Security可以在一些视图技术中进行控制显示效果。例如:JSP或Thymeleaf。在非前后端分离且使用Spring Boot的项目中多使用Thymeleaf作为视图展示技术。
Thymeleaf对Spring Security的支持都放在thymeleaf-extras-springsecurityX中,所以需要在项目中添加此jar包的依赖和thymeleaf的依赖。
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<!--如果你的spring-web/spring-webmvc的是4.X.X版本的,推荐用thymeleaf-extras-springsecurity4的-->
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在html页面中引入security命名空间
<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security" >
1、获取属性
页面获取security中的属性,一般从UsernamePasswordAuthenticationToken类中获取,通过sec:authentication=""获取getXXX的内容,同时包括父类getXXX的内容。
UsernamePasswordAuthenticationToken类中的getXXX方法
父类AbstractAuthenticationToken中的getXXX方法
1、1:实现步骤
1、1、1:新建index.html
在项目resources中新建templates文件夹,在templates中新建index.html页面
在index.html中编写下面内容,测试获取到的值
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录账号:<span sec:authentication="name">123</span><br/>
登录账号:<span sec:authentication="principal.username">456</span><br/>
凭证:<span sec:authentication="credentials">456</span><br/>
权限和角色:<span sec:authentication="authorities">456</span><br/>
客户端地址:<span sec:authentication="details.remoteAddress">456</span><br/>
sessionId:<span sec:authentication="details.sessionId">456</span><br/>
</body>
</html>
1、1、2:编写控制器
thymeleaf页面需要控制转发,在控制器类中编写下面方法
@RequestMapping("/main")
public String demo(){
return "index";
}
2、 权限判断
在html页面中可以使用sec:authorize=”表达式”进行权限控制,判断是否显示某些内容。表达式的内容和access(表达式)的用法相同。如果用户具有指定的权限,则显示对应的内容;如果表达式不成立,则不显示对应的元素。
2、1:不同权限的用户显示不同的按钮
2、1、1:设置用户角色和权限
设定用户具有admin,/insert,/delete权限ROLE_abc角色。
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_abc,/insert,/delete"));
2、1、2:控制页面显示效果
在页面中根据用户权限和角色判断页面中显示的内容
通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/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>
十五、退出登陆
用户只需要向Spring Security项目中发送/logout退出请求即可。
<a href="/logout">退出登录</a>
为了实现更好的效果,通常添加退出的配置。默认的退出url为/logout,退出成功后跳转到/login?logout
如果不希望使用默认值,可以通过下面的方法进行配置类中修改即可。
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login.html");
十六、SpringSecurity中CSRF
刚开始学习Spring Security时,配置类中存在这么一行代码:
/**
* 关闭csrf保护
*/
http.csrf().disable();
如果没有这行代码导致用户无法被认证。这行代码的含义是:关闭csrf防护。
1、什么是CSRF
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack” 或者Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。
跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。
下面三种都为http://localhost:8080/abc的跨域请求
https://localhost:8080/abc
http://127.0.0.2:8080/abc
http://localhost:8081/abc
客户端与服务进行交互时,由于http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持,通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。
2、Spring Security中的CSRF
从Spring Security4开始CSRF防护默认开启。默认会拦截请求。进行CSRF处理。CSRF为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf值为token(token在服务端产生)的内容,如果token和服务端的token匹配成功,则正常访问。
2、1:实现步骤
2、1、1:编写控制方法
编写控制器方法,跳转到templates中login.html页面。
注意:需要在SpringSecurityConfig中添加/showLogin的认证逻辑以及权限设置
@GetMapping("/showLogin")
public String showLogin() {
return "login";
}
2、1、2:修改login.html页面
修改已有的long.html页面
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action = "/login" method="post">
<!--接收_csrf值,由服务端产生-->
<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
2、1、3:修改配置类
在配置类中注释掉CSRF防护失效
// http.csrf().disable();
2、2:开启CSRF后的注销
如果我们开启了CSRF保护机制,则默认情况下,不能使用get方式的/logout
官方的说明(原文地址:https://docs.spring.io/spring-security/site/docs/5.4.1/reference/html5/#servlet-csrf-considerations):
翻译一下就是最简单的方式就是使用form表单退出,如果非要使用一个链接退出,可以通过JavaScript模拟一个表单,设置post退出。如果非要使用get方式退出,上图中官方也给出了代码
我们可以这么解决:
<form style="float: right" action="/logout" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<button type='submit'>退出</button>
</form>
最后
以上就是冷傲小懒虫为你收集整理的Spring Boot整合Spring security的全部内容,希望文章能够帮你解决Spring Boot整合Spring security所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复