目录
[一、Spring Security核心概念](#一、Spring Security核心概念)
[1. 认证(Authentication)](#1. 认证(Authentication))
[2. 授权(Authorization)](#2. 授权(Authorization))
[1. 前端登录流程](#1. 前端登录流程)
[2. 后端认证流程](#2. 后端认证流程)
[1. 用户权限获取](#1. 用户权限获取)
[2. 前端权限指令](#2. 前端权限指令)
[1. 菜单树构建](#1. 菜单树构建)
[2. 前端路由处理](#2. 前端路由处理)
[1. @PreAuthorize注解使用](#1. @PreAuthorize注解使用)
[2. 编程式权限验证](#2. 编程式权限验证)
[3. 公开接口配置](#3. 公开接口配置)
本文介绍了SpringSecurity的核心概念和实现方案。主要内容包括:
1)认证与授权机制,支持多种认证方式和基于角色的访问控制;
2)安全配置示例,展示如何配置CSRF防护、会话管理和权限规则;
3)完整的用户登录流程实现,从前后端交互到Token生成;
4)权限数据加载机制,包括菜单权限和角色权限的获取方式;
5)动态菜单路由的构建与前端处理;
6)权限注解的使用方法,包括@PreAuthorize注解和编程式权限验证。文中提供了详细的代码示例,涵盖了从基础配置到高级权限控制的完整解决方案。
一、Spring Security核心概念
1. 认证(Authentication)
-
验证用户身份("你是谁")
-
支持多种认证方式:用户名密码、OAuth2、JWT等
2. 授权(Authorization)
-
验证用户权限("你能做什么")
-
基于角色和权限的访问控制
二、Security核心配置
java
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login", "/register", "/captchaImage").permitAll()
.antMatchers(HttpMethod.GET, "/**/*.css", "/**/*.js").permitAll()
.antMatchers("/swagger-ui.html", "/druid/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder());
}
}
三、用户登录认证流程
1. 前端登录流程
java
login.vue → store/user.js → api/login.js → 发送请求
2. 后端认证流程
java
SysLoginController → SysLoginService → UserDetailsServiceImpl → 生成Token
核心代码实现:
java
@Service
public class SysLoginService {
public String login(String username, String password, String code) {
// 1. 验证码校验
validateCaptcha(code);
// 2. 用户身份验证
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
// 3. 创建登录用户对象
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
// 4. 记录登录日志
recordLoginLog(username, Constants.LOGIN_SUCCESS, "登录成功");
// 5. 生成JWT Token
return tokenService.createToken(loginUser);
}
}
四、权限数据加载机制
1. 用户权限获取
java
@Service
public class SysPermissionService {
public Set<String> getMenuPermission(LoginUser loginUser) {
if (loginUser.getUser().isAdmin()) {
// 管理员拥有所有权限
return Collections.singleton("*:*:*");
}
// 查询用户菜单权限
return menuService.selectMenuPermsByUserId(loginUser.getUser().getUserId());
}
public Set<String> getRolePermission(LoginUser loginUser) {
// 查询用户角色
return roleService.selectRolePermissionByUserId(loginUser.getUser().getUserId());
}
}
2. 前端权限指令
html
<!-- 权限字符串控制 -->
<el-button v-hasPermi="['system:user:add']">新增用户</el-button>
<el-button v-hasPermi="['system:user:edit', 'system:user:update']">编辑</el-button>
<!-- 角色控制 -->
<el-button v-hasRole="['admin']">管理员功能</el-button>
<el-button v-hasRole="['user', 'editor']">编辑功能</el-button>
五、动态菜单路由加载
1. 菜单树构建
java
@Service
public class SysMenuServiceImpl implements ISysMenuService {
public List<SysMenu> selectMenuTreeByUserId(Long userId) {
List<SysMenu> menus;
if (SecurityUtils.isAdmin(userId)) {
menus = menuMapper.selectMenuTreeAll();
} else {
menus = menuMapper.selectMenuTreeByUserId(userId);
}
return getChildPerms(menus, 0);
}
private List<SysMenu> getChildPerms(List<SysMenu> list, int parentId) {
List<SysMenu> returnList = new ArrayList<>();
for (SysMenu menu : list) {
if (menu.getParentId() == parentId) {
recursionFn(list, menu);
returnList.add(menu);
}
}
return returnList;
}
}
2. 前端路由处理
javascript
// permission.js 路由守卫
router.beforeEach(async (to, from, next) => {
// 获取用户信息
const hasToken = getToken();
if (hasToken) {
if (to.path === '/login') {
next({ path: '/' });
} else {
const hasRoles = store.getters.roles && store.getters.roles.length > 0;
if (hasRoles) {
next();
} else {
try {
// 获取用户信息
const { roles } = await store.dispatch('user/getInfo');
// 生成动态路由
const accessRoutes = await store.dispatch('permission/generateRoutes', roles);
// 添加路由
router.addRoutes(accessRoutes);
next({ ...to, replace: true });
} catch (error) {
await store.dispatch('user/resetToken');
next(`/login?redirect=${to.path}`);
}
}
}
}
});
六、权限注解使用详解
1. @PreAuthorize注解使用
java
@RestController
@RequestMapping("/system/user")
public class SysUserController {
// 验证单个权限
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user) {
// ...
}
// 验证多个权限中的任意一个
@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')")
@PostMapping
public AjaxResult add(@RequestBody SysUser user) {
// ...
}
// 验证角色
@PreAuthorize("@ss.hasRole('admin')")
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds) {
// ...
}
}
2. 编程式权限验证
java
// 检查权限
if (SecurityUtils.hasPermi("sys:user:edit")) {
System.out.println("当前用户有编辑权限");
}
// 检查角色
if (SecurityUtils.hasRole("admin")) {
System.out.println("当前用户是管理员");
}
3. 公开接口配置
java
// 使用@Anonymous注解标记公开接口
@Anonymous
@GetMapping("/public/list")
public List<SomeData> publicList() {
return someService.listPublicData();
}