SpringSecurity基本操作
Spring Security
基本概念
认证
验证身份,比如访问网站或app的登录就是认证。
授权
用户认证后通过后,根据事先分配给用户的权限来控制来控制其访问系统的资源。用户有某个资源的权限可以访问,没有则访问不了。
集成SpringSecurity与登陆认证
-
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.7.3</version> </dependency>
-
继承Security配置类,继承
WebSecurityConfigurerAdapter
类,添加@EnableWebSecurity
注解,重写configure(HttpSecurity http)
,如果使用thymeleaf模板引擎,需要导入依赖,不然跳转页面的时候会出错。注意:SpringBoot2.7 WebSecurityConfigurerAdapter类过期配置
package com.example.springsecurity.config; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * @author Pillar * @version 1.0 * @date 2022/9/13 12:07 */ @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin()//开启登陆 .successForwardUrl("/index");//登陆成功后的页面 } }
@Controller public class IndexController { @RequestMapping("/index") public String Index(){ return "index"; } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> Hello,Srping Sercurity <h1> <a href="/logout">退出登录</a> </h1> </body> </html>
测试结果:根据用户名user和默认生成的密码可以进行登录和退出,但是退出之后,还是可以访问index页面,怎么样才能未登录不能访问主页呢
-
添加
.authorizeRequests().anyRequest().authenticated();
,不登录访问index,直接跳转到登陆页面@Override protected void configure(HttpSecurity http) throws Exception { http. formLogin()// .successForwardUrl("/index")//登陆成功后的页面 .and() .authorizeRequests().anyRequest().authenticated();//认证请求中所有请求都需要被认证 }
-
添加账号,重写
configure(AuthenticationManagerBuilder auth)
,一般从数据库中读取,先测试,先从内存中创建,并且设置无加密,并创建用户@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .passwordEncoder(NoOpPasswordEncoder.getInstance()) .withUser("admin").password("admin").roles("admin") .and() .withUser("mqk").password("mqk").roles("user"); }
-
使用BCrypt加密,先生成加密
public class BCryptTest { private static final String SALT = "$2a$10$iu37mYcqBylrMgloNl33A."; public static void main(String[] args) { //也可以使用自动生成的盐值 BCrypt.gensalt() // System.out.println(BCrypt.gensalt()); System.out.println(BCrypt.hashpw("admin", SALT)); System.out.println(BCrypt.hashpw("mqk", SALT)); } }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .passwordEncoder(new BCryptPasswordEncoder()) .withUser("admin").password("$2a$10$iu37mYcqBylrMgloNl33A.nYpadegA6oRObaQhAzVMLi23knSfLc2").roles("admin") .and() .withUser("mqk").password("$2a$10$iu37mYcqBylrMgloNl33A.hQFKzxTDrEwemaOZT2duk/KURgpH9XG").roles("user"); }
URL访问控制
对应角色只能访问对应资源
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
formLogin()//
.successForwardUrl("/index")//登陆成功后的页面
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")//admin目录下的所有请求都能被admin角色访问
.antMatchers("/user").hasRole("user")//user请求只能被user角色访问
.antMatchers("/**/*.jpg","/**/*.png").hasRole("user")//所有png和jpg图片请求只能被user角色访问
.anyRequest().authenticated();//认证请求中所有请求都需要被认证
}
ANT通配符有三种:
? 匹配任何单字符
* 匹配0或者任意数量的字符
** 匹配0或者更多的目录
角色和授权
看源码
对于角色来说,它的最终结果还是添加权限
public User.UserBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList(roles.length);
String[] var3 = roles;
int var4 = roles.length;
for(int var5 = 0; var5 < var4; ++var5) {
String role = var3[var5];
Assert.isTrue(!role.startsWith("ROLE_"), () -> {
return role + " cannot start with ROLE_ (it is automatically added)";
});
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
return this.authorities((Collection)authorities);
}
在存储级别根本没有角色概念
public static List<GrantedAuthority> createAuthorityList(String... authorities) {
List<GrantedAuthority> grantedAuthorities = new ArrayList(authorities.length);
String[] var2 = authorities;
int var3 = authorities.length;
for(int var4 = 0; var4 < var3; ++var4) {
String authority = var2[var4];
grantedAuthorities.add(new SimpleGrantedAuthority(authority));
}
return grantedAuthorities;
}
权限的使用authorities("img");
hasAnyAuthority("img")
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password("$2a$10$iu37mYcqBylrMgloNl33A.nYpadegA6oRObaQhAzVMLi23knSfLc2").roles("admin")
.and()
.withUser("mqk").password("$2a$10$iu37mYcqBylrMgloNl33A.hQFKzxTDrEwemaOZT2duk/KURgpH9XG").authorities("img");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
formLogin()//
.successForwardUrl("/index")//登陆成功后的页面
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")//admin目录下的所有请求都能被admin角色访问
.antMatchers("/user").hasRole("user")//user请求只能被user角色访问
.antMatchers("/**/*.jpg","/**/*.png").hasAnyAuthority("img")//所有png图片请求只能被user角色访问
.anyRequest().authenticated();//认证请求中所有请求都需要被认证
}
连接数据库登陆认证
- 先准备好根据用户名查询用户信息的mapper方法
数据库
实体类
@Data
public class UserInfo {
private Integer Id;
private String Username;
private String password;
private String role;
}
整合Mybatis
https://www.cnblogs.com/do-it-520/p/springboot_mybatis.html
https://www.cnblogs.com/do-it-520/p/16580064.html
编写mapper
package com.example.springsecurity.mapper;
import com.example.springsecurity.vo.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
/**
* @author Pillar
* @version 1.0
* @date 2022/9/13 18:49
*/
@Mapper
@Repository
public interface UserInfoMapper {
UserInfo selectByUsername(String username);
}
mapper.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">
<mapper namespace="com.example.springsecurity.mapper.UserInfoMapper">
<resultMap id="userResultMap" type="com.example.springsecurity.vo.UserInfo">
</resultMap>
<select id="selectByUsername" resultMap="userResultMap">
select * from userInfo where username = #{username}
</select>
</mapper>
- 实现
UserDetailsService
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserInfoMapper mapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = mapper.selectByUsername(username);
return User.withUsername(userInfo.getUsername()).password(userInfo.getPassword()).roles(userInfo.getRole()).build();
}
}
- 修改配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
***
***
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
}
自定义登陆界面
login_view.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="login" method="post">
<div>账号:<input type="text" name="username"></div>
<div>密码:<input type="password" name="password"></div>
<div><input type="submit" value="登陆"></div>
</form>
</body>
</html>
controller
@RequestMapping("/login_view")
public String Login(){
return "login_view";
}
config
CSRF是指跨站请求伪造(Cross-site request forgery),在项目中添加了
Security
模块后,在发送post请求时会失败,出现以下日志:Invalid CSRF token found for ...
原因 在Security的默认拦截器里,默认会开启CSRF处理,判断请求是否携带了token,如果没有就拒绝访问。并且,在请求为(GET|HEAD|TRACE|OPTIONS)时,则不会开启。
解决 手动关闭csrf
.loginPage("/login_view").permitAll()
需要为登录界面开放权限
.loginProcessingUrl("/login")
登陆请求地址
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()//
.formLogin()//
.loginPage("/login_view").permitAll()//为登陆页面开放权限,无论是谁都可以登录
.loginProcessingUrl("/login")//登陆请求地址
.successForwardUrl("/index")//登陆成功后的页面
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")//admin目录下的所有请求都能被admin角色访问
.antMatchers("/user").hasRole("user")//user请求只能被user角色访问
.antMatchers("/**/*.jpg","/**/*.png").hasRole("user")//所有png图片请求只能被user角色访问
.anyRequest().authenticated();//认证请求中所有请求都需要被认证
}
继承Thymeleaf
<!DOCTYPE html>
<!--导入命名空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Hello,Srping Sercurity
<div sec:authorize="isAuthenticated()">
<h1>
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</h1>
</div>
<!--所有的html元素都可以被thymeleaf接管 th:元素名-->
<!--如果未登录-->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/login}">
<i class="address card icon"></i> 登录
</a>
</div>
<!--如果已登录-->
<!--用户名,注销-->
<div sec:authorize="isAuthenticated()">
<a class="item">
用户名: <span sec:authentication="name"></span>
角色: <span sec:authentication="principal.authorities"></span>
</a>
</div>
</body>
</html>
登陆过期时间设置
server:
servlet:
session:
timeout: 最低60秒,低于60按60处理。默认1800秒,就是30分钟
设置超时的特定页面
@Override
protected void configure(HttpSecurity http) throws Exception {
.and()
.sessionManagement()
.invalidSessionUrl("/timeout");
}
记住我
@Override
protected void configure(HttpSecurity http) throws Exception {
.and()
.rememberMe()
.tokenValiditySeconds(2000);
}
.and()
.rememberMe()
.tokenValiditySeconds(2000)
.tokenRepository(persistentTokenRepository());
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
//tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
根据setCreateTableOnStartup中init方法手动创建数据库表,因为init方法为保护方法,不继承用不了,所以直接将setCreateTableOnStartup(true)注释掉。
登陆完之后
本文来自博客园,作者:NeverLateThanBetter,转载请注明原文链接:https://www.cnblogs.com/do-it-520/p/16691324.html
韶华易逝,不能虚度年华。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· 10亿数据,如何做迁移?
· 推荐几款开源且免费的 .NET MAUI 组件库
· c# 半导体/led行业 晶圆片WaferMap实现 map图实现入门篇
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!