网页右边,向下滑有目录索引,可以根据标题跳转到你想看的内容 |
如果右边没有就找找左边 |
- Spring Security是一个高度自定义的安全框架。利用Spring IOC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。
- 使用Spring Security的原因:javaEE的Servlet规范或EJB规范中的安全功能缺乏典型企业应用场景。同时认识到他们在WAR或EAR级别无法移植。如果你更换服务器环境,需要花费大量精力重新配置你的应用程序。使用Spring Security解决了这些问题,也提供了很多可定制的安全功能
- Spring Security重要核心功能:“认证"和"授权”。
- 通俗讲,认证就是你能不能进我家,授权就是,你进我家能干什么,比如我就授权你在我家客厅随意活动,那么其它地方你就不能去,也不能操作
- 不通俗讲,认证:是建立一个声明主体的过程(一个主体代表一个用户、设备或一些可以在你程序中执行动作的其它系统),决定这些主体是否可以登录系统。授权:确定一个主体是否允许在你的应用程序中执行一个动作的过程。就是判断用户是否有权限去做某些事
- 2003年底,以“The Acegi Security System for Spring”的名字出现,前身为acegi项目
- 起因是Spring开发者邮件列表中的一个问题:有人提问是否考虑提供一个基于Spring的安全实现。之后因为时间问题,开发出了一个简单的安全实现,没有深入研究
- 几周后,Spring社区中其他成员同样询问安全问题,就将代码开源了出去。
- 2004年1月左右,20人左右使用这个项目,随着更多人的加入,2004年3月左右,sourceforge中建立了一个项目acegi,这时并没有认证模块,所有认证功能都是依赖容器完成。而acegi则注重授权。
- 之后使用的人越来越多,基于容器实现认证显现不足,acegi中也加入了认证功能。一年后,acegi成为Spring子项目
- 2006年5月,acegi 1.0.0版本发布。2007年底acegi更名为Spring Security
一、先写一个简单Spirng Security项目
- 和普通spring boot项目不同的地方,就是多引入一个security的包
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.11.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
- 启动类
- controller(简单编写,然后运行项目,复制打印到控制台的密码)
二、自定义认证等逻辑
1. UserDetailsService自定义登录逻辑
- 由前面的案例可知,我们什么都没有配置时,账号密码都是Spring Security帮我们生成,实际项目都是查数据库,所以我们需要自定义逻辑,控制认证。
- 需要自定义逻辑非常简单,只需要实现UserDetailsService即可
- UserDetailsService
- 可见这个接口,只定义个一个方法,根据方法名,可以判断是根据用户名,加载用户,
返回值是UserDetails
类型
- UserDetails
它封装了用户的基本信息
但它是一个接口,想要返回它,必须通过实现类
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
import org.springframework.security.core.GrantedAuthority;
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 java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(!username.equals("admin")){
throw new UsernameNotFoundException("用户不存在!!!");
}
String password = "pwd";
List<String> list = new ArrayList<>();
list.add("admin1");
list.add("admin2");
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0;i<list.size();i++){
if(i==(list.size()-1)){
stringBuilder.append(list.get(i));
}else{
stringBuilder.append(list.get(i)+",");
}
}
System.out.println(stringBuilder.toString());
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(stringBuilder.toString());
UserDetails userDetails = new User(username, password, grantedAuthorities);
return userDetails;
}
}
- 提示,密码必须是编码加密后的,不能用不编码加密的(其实回头看之前,它提供的密码,就能发现,提供的是编码加密后的密码)
2. PasswordEncoder密码解析器
- Spring Security要求容器中必须有PasswordEncoder实例。所以当自定义登录逻辑时,要求必须给容器注入PasswordEncoder的bean对象
- PasswordEncoder接口
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
- 同样因为它是接口,我们需要实现类来创建它
- BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时多使用这个解析器。
- BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认10。
2. 创建Security配置类,配置解析器Bean实例,然后注入Spring IOC容器,这是自定义登录逻辑的硬性要求 |
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
protected PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
@Autowired
private PasswordEncoder passwordEncoder;
String password = "pwd";
String encodePassword = passwordEncoder.encode(password);
System.out.println(password+"加密后-------------"+encodePassword);
boolean matches = passwordEncoder.matches(password, encodePassword);
System.out.println("原密码和加密后是否匹配-------"+matches);
UserDetails userDetails = new User(username, encodePassword, grantedAuthorities);
3. 连接数据库实现自定义登录逻辑
- 依赖
<!--myBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!--apace commons工具-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
- mapper接口
- xml映射文件
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yzpnb.mapper.TUserMapper">
<select id="selectByUsername" parameterType="java.lang.String" resultType="com.yzpnb.pojo.TUser">
select id,username,password from t_user where username=#{username}
select>
mapper>
- 配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.47.1:3306/dubbo_demo?serverTimezone=CST&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
mybatis:
type-handlers-package: com.yzpnb.pojo
mapperLocations: classpath:mybatis/*Mapper.xml
- 启动类
import com.yzpnb.mapper.TUserMapper;
import com.yzpnb.pojo.TUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private TUserMapper tUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
TUser tUser = tUserMapper.selectByUsername(username);
if(tUser == null){
System.out.println("用户不存在");
throw new UsernameNotFoundException("用户不存在");
}
System.out.println("查询到用户信息------用户名:"+username+"--------密码:"+tUser.getPassword());
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin1,admin2");
UserDetails userDetails = new User(username, tUser.getPassword(), grantedAuthorities);
return userDetails;
}
}
4. 自定义登录页面
- 引入thymeleaf依赖
- 在resources下创建templates文件夹,然后创建login.html页面编写代码作为登录页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<form action="/login" method="post">
username:<input type="text" name="username"/><br/>
password:<input type="password" name="password"/><br/>
<input type="submit"/><br/>
form>
body>
html>
- 再创建一个index.html 作为登录成功后,转到的页面
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SpringSecurityDemoController {
@RequestMapping("showLogin")
public String showLogin(){
return "login";
}
@RequestMapping("loginSuccess")
public String loginSuccess(){
return "index";
}
}
1. 重点(Spring Security最核心地方)配置类修改
- 配置类中,设置登录页面。需要继承WebSecurityConfigurerAdapter,并重写configure方法,常用设置如下
- successForwardUrl():登录成功后跳转地址
- loginPage():登录页面
- loginProcessingUrl():登录页面表单提交地址,此地址可以不真实存在
- antMatchers():匹配内容
- permitAll():允许
- authorizeRequests():授权相关的
- formLogin():所有和表单有关系的操作
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 {
@Bean
protected PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/showLogin")
.loginProcessingUrl("/login")
.successForwardUrl("/loginSuccess");
http.authorizeRequests()
.antMatchers("/showLogin").permitAll()
.anyRequest().authenticated();
http.csrf().disable();
}
}
- 访问showLogin,会直接放行,然后进入controller,在里面直接重定向到了登录页面
- 登录成功,地址栏会显示我们设定URL,同时请求/login,那么检测到url中有login,就会执行我们自定义登录逻辑,然后重定向到/loginSuccess,这个请求里面重定向了index.html页面
2. 登录失败页面
- 我们上面如果登录失败,依然会在登录页面,现在我们想实现,登录失败,进入错误页面
http.formLogin()
.loginPage("/showLogin")
.loginProcessingUrl("/login")
.successForwardUrl("/loginSuccess")
.failureForwardUrl("/loginFail")
;
3. 自定义登录参数
- 前面我们讲,必须用username和password两个参数,那么也可以通过参数设置
http.formLogin()
.loginPage("/showLogin")
.loginProcessingUrl("/login")
.successForwardUrl("/loginSuccess")
.failureForwardUrl("/loginFail")
.usernameParameter("un")
.passwordParameter("pwd")
;
4. 解决重复提交表单问题,并且可以站外转发(前后端分离项目使用)
- 当我们登录成功进入页面,刷新时,会提示重新提交表单,接下来解决这个问题
- 首先,页面跳转,会重发请求,但是重定向不会,所以我们当前使用的都是跳转,我们需要改成重定向
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.sendRedirect("/loginSuccess");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.sendRedirect("/loginFail");
}
})
http.authorizeRequests()
.antMatchers("/showLogin","/loginFail").permitAll()
.anyRequest().authenticated();
那么如何站外跳转呢?毕竟现在都是前后端分离项目,页面不是和你项目写在一起的 |
.successHandler(new AuthenticationSuccessHandler() {
@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("http://www.baidu.com");
}
})
5. 介绍一些和url,静态资源有关的东西,不常用就不详细介绍了
- public C antMatchers(String… antPatterns):前面我们的授权配置方法
- 可见参数是不定长参数,用于匹配URL规则,如下:
- ?:匹配一个字符
- *:匹配0个或多个字符
- **:匹配0个或多个目录
- 实际项目中,需要放行所有静态资源时,就可以这样写,比如放行js文件夹下所有js脚本
.antMatchers("/js/**").permitAll()
.antMatchers("/**/*.js").permitAll()
6. 异常403处理方案
- 使用Spring Security 时经常会看见403(无权限),默认情况下显示效果如下
- 而实际项目中可以都是一个异步请求,显示上述效果对用户很不友好(比如整个系统,你只有一个菜单没有权限进去,这时你不小心点了一些,直接跳出一个403页面,感觉是很不好的,一般都是弹出一个对话框,提示一下没有权限),Spring Security支持自定义权限受限
1. 设置一个拒绝一切用户访问的url,以实现403页面的出现 |
.antMatchers("abc").denyAll()
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@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();
}
}
- 先引入组件
- 配置异常处理
http.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler);
- 另外,同步的话,就是不前后端分离,也可以写一个controller来处理,但现在基本没用
http.exceptionHandling()
.accessDeniedPage("/showException");
三、自定义授权、访问控制等逻辑
1. 利用角色的权限,实现限制url
- Spring Security提供了很多权限控制,比如一个用户登录后,必须具备admin1权限,才可访问/successLogin页面,如果它只有admin2权限,那么就无法访问
- 用户权限是在自定义登录逻辑中,创建User对象时指定的,权限字符严格区分大小写
1. 配置url访问权限的常用方法(在配置类中配置) |
- hasAuthority(String):必须具备指定
权限
,才可访问
.antMatchers("/loginSuccess").hasAuthority("admin1")
- hasAnyAuthority(String…):具备给定
权限
中的某一个,就可以访问
.antMatchers("/loginSuccess").hasAnyAuthority("admin1","admin2")
- hasRole(String):具备给定
角色
就可以访问,否则出现403
- 给用户赋予角色,也是创建User对象时指定,和权限放在一起即可
- 赋予角色时,需要以ROLE_开头,后面添加角色名,例如ROLE_manager,其中manager是角色名,ROLE_是固定的前缀。(因为和权限放在一起,程序分不清谁是权限,谁是角色,所以角色加个前缀,方便区分)
- 但是使用hasRole()这个方法时,参数直接传manager,前缀ROLE_不需要指定,否则报错
.antMatchers("/loginSuccess").hasRole("manager")
- hasAnyRole(String…):具备给定
角色
中的某一个,就可以访问
- haslpAddress(String):请求的是
指定ip
,就可以访问
- 可以通过request.getRemoteAddr()获取ip
- 注意:本机测试时,localhost和127.0.0.1输出ip地址不一样
- 当浏览器通过localhost进行访问时,控制台打印内容是getRemoteAddr:0:0:0:0:0:0:0:1
- 当浏览器通过127.0.0.1访问时控制台打印内容时:getRemoteAddr:127.0.0.1
- 如果是普通ip192.168.xxx.xxx之类的,都会原样输出:getRemoteAddr:192.168.xxx.xxx
2. 设置访问/loginSuccess这个url必须具有admin1权限,访问/abc必须具有admin3 |
- 封装权限
- 配置
3. 设置访问/loginSuccess这个url必须具有manager角色,访问/abc必须具有abc角色 |
- 配置类
4. 设置访问/loginSuccess这个url必须使用127.0.0.1ip,访问/abc必须使用ip192.168.0.1 |
2. 连接数据库实现权限认证
1. rbac表设计
- 这个表可能有些难理解,结合想要实现的场景来分析一些
- 首先一个系统,有很多菜单,比如用户管理,用户管理下有很多子菜单,比如学生管理,讲师管理
- 那么如何确定父子关系呢,这里就是采用码表的方式,通过父id来确定自己的父亲
- 顶级菜单父id为0,它的子菜单的父id就是这个顶级菜单的id,依次类推,除了顶级菜单父id为0外,其它菜单项的父id都会记录自己父菜单的id
- 除了菜单还有很多按钮,比如学生管理菜单下,有新增,删除按钮,那么必须在数据库指定这些菜单的类型,所以我们通过一些特定字符来表示一个菜单的类型,比如menu就是菜单,button就是按钮,或者分的更细,student就是操作学生的按钮
- 每个菜单都应该具备自己的权限,只有用户拥有相应权限,才能操作菜单,所以数据库中应该有一个权限字段,保存菜单权限
- 额外的,我们想实现,用户进入系统,只能看到自己有权限操作的菜单,比如一个普通用户,只能查看菜单,不能使用增删改查之类的操作按钮
- 那么我们就应该建立一个关系表,决定一个角色能够操作哪些菜单
2. mapper代码,实现对rbac表的数据库操作
为了省事,就不给每张表建立实体类了,直接用List |
- sql语句测试
- xml文件编写
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yzpnb.mapper.TUserMapper">
<select id="selectByUsername" parameterType="java.lang.String" resultType="com.yzpnb.pojo.TUser">
select id,username,password from t_user where username=#{username}
select>
<select id="selectRoleById" parameterType="java.lang.Integer" resultType="java.lang.String">
select
name
from
t_user_role as tur
left join
t_role as r
on
tur.rid = r.id
where
tur.uid = #{userId}
select>
<select id="selectPermissionsByUserId" parameterType="java.lang.Integer" resultType="java.lang.String">
select
m.permission as permission
from
t_user_role as tur
left join
t_role as r
on
tur.rid = r.id
left join
t_role_menu as trm
on
r.id = trm.rid
left join
t_menu as m
on
m.id = trm.mid
where
tur.uid = #{userId}
select>
mapper>
3. 重写登录授权逻辑代码和配置类,测试
List<String> roles = tUserMapper.selectRoleById(tUser.getId());
List<String> permissions = tUserMapper.selectPermissionsByUserId(tUser.getId());
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0;i<roles.size();i++){
if(i == roles.size()-1) {
if(permissions.size()>0) stringBuilder.append("ROLE_"+roles.get(i)+",");
else stringBuilder.append("ROLE_"+roles.get(i));
}else
stringBuilder.append("ROLE_"+roles.get(i)+",");
}
for (int i = 0;i<permissions.size();i++){
if(i == permissions.size()-1) stringBuilder.append(permissions.get(i));
else stringBuilder.append(permissions.get(i)+",");
}
List<GrantedAuthority> grantedAuthorities =
AuthorityUtils.commaSeparatedStringToAuthorityList(stringBuilder.toString());
System.out.println("用户的权限字符为:"+stringBuilder);
.antMatchers("/loginSuccess").hasRole("管理员")
.antMatchers("/abc").hasAuthority("demo:insert")
3. 基于表达式的访问控制
- 之前学习的登录用户权限判断实际上底层都是调用access(表达式),例如hasRole,hasAnyRole等,底层都是调用access()方法,
- 因为实际项目中,很可能出现Spring Security提供的方法实现不了的需求,这时就要我们通过这个方法,自定义访问控制方法
- 接口
- 实现类,实现判断权限列表中是否包含角色管理员,如果有返回true,没有返回false,记住通过@Service注解,将其注入到容器中
import com.yzpnb.service.MyAccessService;
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 MyAccessServiceImpl implements MyAccessService {
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object o = authentication.getPrincipal();
if(o instanceof UserDetails){
UserDetails userDetails = (UserDetails) o;
Collection<? extends GrantedAuthority> collection = userDetails.getAuthorities();
return collection.contains(new SimpleGrantedAuthority("ROLE_管理员"));
}
return false;
}
}
- 我们前面讲过,access()方法可以通过表达式控制
- 那么在@Service注解没有参数的情况下,我们刚才的接口匹配的表达式就是
“@myAccessServiceImpl.hasPermission(request,authentication)”
- 接下来看怎么具体使用
.antMatchers("/loginSuccess").access("@myAccessServiceImpl.hasPermission(request,authentication)")
4. 基于注解的访问控制
- 虽然提供了一些访问控制注解,但默认是不可用的,需要通过在启动类添加@EnableGlobalMethodSecurity()注解开启后,才能使用,
注意,每个注解,都需要单独开启,而且不能共存
- 这些注解可以写到Service接口或方法上,也可以写到Controller或Controller中的方法上,一般写在Controller的方法上,当客户端请求Controller某一方法,就会进行访问控制
- 效果就是,条件运行,程序正常执行,条件不允许,会报错500
- @Secured:判断是否具有直接角色,参数要以ROLE_开头,可以写在方法或类上
- @PreAuthorize:访问方法或类之前先判断权限,使用较多,参数就和配置类中使用方法一样
- @PostAuthorize:方法或类执行结束后判断权限,此注解很少使用,不做讲解
- 先放行请求
- 启动类开启注解访问控制功能
@EnableGlobalMethodSecurity(securedEnabled = true)
- 测试@Secured(“ROLE_管理员”)
- 开启注解
- 测试
5. Remember Me 记住我功能
- 用户只需要在登录时添加remember-me复选框,取值为true,那么框架会自动把用户信息存储到数据源中,以后就可以不重复登录访问
- 需要添加额外依赖,底层依赖Spring-JDBC,但是实际开发中多使用MyBatis框架,而很少直接导入spring-jdbc,所以我们只需要mybatis启动器(这些依赖我们前面都已经加过了)
1. 编写配置类,并注入Bean对象,这里涉及到了自动建表,第一次需要建立,第二次就不需要了,所以我们自动建表一般都手动建立,如果使用自动建立,那么第二次运行需要注释掉自动建表代码 |
- 配置类
package com.yzpnb.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
private DataSource dataSource;
@Bean
protected PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
- spring security 配置类,将登录逻辑和刚刚配置的bean实例注入,配置好,再配置令牌失效时间为120秒
@Autowired
private PersistentTokenRepository persistentTokenRepository;
@Autowired
private UserDetailsServiceImpl userDetailsService;
http.rememberMe()
.tokenValiditySeconds(120)
.userDetailsService(userDetailsService)
.tokenRepository(persistentTokenRepository);
3. 重写登录页面,添加Remember me 复选框 |
remember-me:<input type="checkbox" name="remember-me" value="true"/>
- 运行后,注释自动建表代码
- 查看效果(数据库令牌添加成功后,无论重启项目多少次,都无需重复登录,只有超过有效时间后才需要重新登录)
四、退出登录
- 用户只需要发送/logout请求即可,spring security默认退出登录请求为/logout,退出成功后跳转到/login?logout
- 实现非常简单,在页面中添加/logout超链接即可
<a href="/logout"/>退出登录
- 但是通常我们为了实现更好的退出效果,会添加退出配置,
http.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.sendRedirect("/showLogin");
}
});
五、Spring Security中的CSRF
- 还记得自定义登录配置时的csrf代码吗?没有内一行代码会导致用户无法被认证
- 这行代码的含义是,关闭csrf防护
http.csrf().disable();
- 跨站请求伪造,也称One Click Attack或者Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。
- 跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求
- 客户端与服务器进行交互时,由于http协议本身是无状态协议,所以引入cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份。
- 跨域情况下,session id可能会被第三方恶意劫持,通过这个session id向服务端发起请求时,服务端会认为这个请求合法,可能会被黑客利用
- Spring Security4开始CSRF防护默认开启,默认拦截请求,进行CSRF处理。CSRF为了保证不是其它第三方网站访问,要求访问时携带参数名为_csrf,值为token(token在服务端产生)的内容,如果token和服务端保存的token不一致,那么拒绝访问。
- 所以如果你想使用CSRF的保护,那么你每次发送请求,只要发送一个参数名为_csrf的参数即可,至于参数值,是一个token字符串,需要服务器去生成,返回给你
六、Oauth2 、SpringSecurityOauth2
由于篇幅原因,我将其放在这篇文章中https://blog.csdn.net/grd_java/article/details/121902912 |
七、前后端分离,实现登录,权限管理系统
因为篇幅原因,我将其放在这篇文章中https://blog.csdn.net/grd_java/article/details/121925792 |