SpringSecurity之入门程序-动态控制
官方文档
https://spring.io/projects/spring-security/
安全框架
核心:用户认证,用户授权
1、解决分布式权限安全问题
2、与spring无缝
3、重量级,多依赖
入门案例
1、数据库准备
数据库设计-RBAC
2、SpringBoot项目基础搭建
导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- springboot整合freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-->spring-boot 整合security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- springboot 整合mybatis框架 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- alibaba的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
创建实体类
// 角色信息表
@Data
public class RoleEntity {
private Integer id;
private String roleName;
private String roleDesc;
}
// 用户信息表
@Data
public class UserEntity implements UserDetails {
private Integer id;
private String username;
private String realname;
private String password;
private Date createDate;
private Date lastLoginTime;
private boolean enabled;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
// 用户所有权限
private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
}
用户实体类继承UserDetails ,使用权限列表的容器
// 权限表
@Data
public class PermissionEntity {
private Integer id;
// 权限名称
private String permName;
// 权限标识
private String permTag;
// 请求url
private String url;
}
配置文件-YML格式
# 配置freemarker
spring:
freemarker:
# 设置模板后缀名
suffix: .ftl
# 设置文档类型
content-type: text/html
# 设置页面编码格式
charset: UTF-8
# 设置页面缓存
cache: false
# 设置ftl文件路径
template-loader-path:
- classpath:/templates
# 设置静态文件路径,js,css等
mvc:
static-path-pattern: /static/**
datasource:
name: test
url: jdbc:mysql://127.0.0.1:3306/mayikt_rbac
username: root
password: 123123
# druid 连接池
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
前端页面准备
登陆界面
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<h1>SpringSecurity权限控制登陆系统</h1>
<form action="/login" method="post">
<span>用户名称</span><input type="text" name="username"/> <br>
<span>用户密码</span><input type="password" name="password"/> <br>
<input type="submit" value="登陆">
</form>
<#if RequestParameters['error']??>
用户名称或者密码错误
</#if>
</body>
</html>
首页界面
<h1>权限控制页面首页</h1>
创建启动类
@MapperScan("com.mayikt.mapper")
@SpringBootApplication
public class AppSecurity {
public static void main(String[] args) {
SpringApplication.run(AppSecurity.class);
}
}
创建服务类实现 UserDetailsService接口
完成账户的权限查询及放入
@Component
@Slf4j
public class MemberUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
/**
* loadUserByUserName
* 登录成功进入
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1.根据该用户名称查询在数据库中是否存在
UserEntity userEntity = userMapper.findByUsername(username);
if (userEntity == null) {
return null;
}
// 2.查询对应的用户权限
List<PermissionEntity> listPermission = userMapper.findPermissionByUsername(username);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
// Lambda表达式
listPermission.forEach(user -> {
//将权限添加到列表
authorities.add(new SimpleGrantedAuthority(user.getPermTag()));
});
log.info(">>>authorities:{}<<<", authorities);
// 3.将该权限添加到security
userEntity.setAuthorities(authorities);
return userEntity;
}
// public static void main(String[] args) {
// UserDetailsService userDetailsService = (username) -> {
// return null;
// };
// }
}
账户查询设计数据库:
Mapper层
用户信息查询
public interface UserMapper {
/**
* 根据用户名称查询
*
* @param userName
* @return
*/
@Select(" select * from sys_user where username = #{userName}")
UserEntity findByUsername(@Param("userName") String userName);
/**
* 查询用户的权限根据用户查询权限
*
* @param userName
* @return
*/
@Select(" select permission.* from sys_user user" + " inner join sys_user_role user_role"
+ " on user.id = user_role.user_id inner join "
+ "sys_role_permission role_permission on user_role.role_id = role_permission.role_id "
+ " inner join sys_permission permission on role_permission.perm_id = permission.id where user.username = #{userName};")
List<PermissionEntity> findPermissionByUsername(@Param("userName") String userName);
}
权限信息
public interface PermissionMapper {
@Select(" select * from sys_permission ")
List<PermissionEntity> findAllPermission();
}
配置类中回调并验证,设置拦截
@Component
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MemberUserDetailsService memberUserDetailsService;
@Autowired
private PermissionMapper permissionMapper;
/**
* 添加授权账户
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// // 设置用户账号信息和权限
// auth.inMemoryAuthentication().withUser("mayikt_admin").password("mayikt")
// .authorities("addMember", "delMember", "updateMember", "showMember");
// // 如果mayikt_admin账户权限的情况 所有的接口都可以访问,如果mayikt_add 只能访问addMember
// auth.inMemoryAuthentication().withUser("mayikt_add").password("mayikt")
// .authorities("addMember");
//回调加权限。转化加密,验证
auth.userDetailsService(memberUserDetailsService).passwordEncoder(new PasswordEncoder() {
/**
* 对密码MD5加密处理
* @param rawPassword
* @return
*/
@Override
public String encode(CharSequence rawPassword) {
return MD5Util.encode((String) rawPassword);
}
/**
* rawPassword 用户输入的密码
* encodedPassword 数据库DB的密码
*
* @param rawPassword
* @param encodedPassword
* @return
*/
public boolean matches(CharSequence rawPassword, String encodedPassword) {
//转化用户登录密码MD5
String rawPass = MD5Util.encode((String) rawPassword);
//比较
boolean result = rawPass.equals(encodedPassword);
return result;
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// //配置httpBasic Http协议认证
//// http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();
// //配置httpBasic Http协议认证
// http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and()
// .formLogin();
// // 默认fromLog
List<PermissionEntity> allPermission = permissionMapper.findAllPermission();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
expressionInterceptUrlRegistry = http.authorizeRequests();
allPermission.forEach((permission) -> {
expressionInterceptUrlRegistry.antMatchers(permission.getUrl()).
hasAnyAuthority(permission.getPermTag());
});
expressionInterceptUrlRegistry.antMatchers("/login").permitAll()//放行
.antMatchers("/**").fullyAuthenticated()//拦截所有
.and().formLogin().loginPage("/login").and().csrf().disable();//跳转向登陆界面
//.antMatchers("/addMember") 配置addMember请求权限 hasAnyAuthority("addMember")
}
/**
* There is no PasswordEncoder mapped for the id "null"
* 原因:升级为Security5.0以上密码支持多中加密方式,恢复以前模式
*
* @return
*/
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
}
权限实现思路
账户登录时配置类SecurityConfig接收请求
configure(AuthenticationManagerBuilder auth)中:
MemberUserDetailsService对象回调,进行账户名查询验证并根据账户名进行权限查询及放入
configure(HttpSecurity http) 中
进行权限验证
转回配置类,进行密码加密处理及验证
返回true,登录成功
一个请求过来时通过各个过滤器,最后通过FilterSecurityInterceptor来判断这个请求url是否是不需要验证的,
如果是就直接访问到我们的接口api,如果不是的话,再判断当前请求线程中是否有authentication的认证对象,
如果有就放行,如果没有就返回登录页面(比如这里我们设置的是登录表单的方式),
来到登录页面输入账号密码登录后就会来到 UsernamePasswordAuthenticationFilter,
经过一系列的操作,最后验证成功就会把认证对象authentication放进securityContext中,
然后FilterSecurityInterceptor判断到当前请求线程中这个认证对象就放行,返回的时候最后会通过securityContextpersistenceFilter,
判断当前线程是否有securityContext,如果有就放进session,
那么下次再请求这个url的时候会首先通过securityContextpersistenceFilter这个过滤器,
判断session中是否有securityContextduxiiang,如果有就放进当前请求线程中,然后最后经过FilterSecurityInterceptor时再判断当前请求线程是否有认证对象,
由于最前面经过securityContextpersistenceFilter,已经从session中把认证对象放进了当前请求线程中,所以FilterSecurityInterceptor会直接放行,这样就访问到我们的接口api。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理