配置代码速览
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
{
return httpSecurity
// CSRF禁用,因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token,所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
.requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
.build();
}
常用参数解释
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
逐行逐段分析
// CSRF禁用,因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
- 关闭csrf攻击防御
默认情况下SpringSecurity开启了csrf攻击防御的功能,这要求请求参数中必须有一个隐藏的_csrf字段,如下:

- 禁用缓存控制:
通过 cacheControl(cache -> cache.disable()) 设置,确保浏览器不会缓存该响应。这意味着每次请求都会从服务器获取最新数据,而不是使用浏览器缓存。 - 设置Frame Options为Same Origin:
通过 frameOptions(options -> options.sameOrigin()) 配置,限制了只有同源页面可以将此页面嵌入到iframe中。这有助于防止点击劫持攻击(clickjacking attacks),提高安全性。
如何理解限制了只有同源页面可以将此页面嵌入到iframe中?
限制只有同源页面可以将此页面嵌入到iframe中意味着只有与当前页面具有相同协议(http或https)、相同域名和相同端口号的页面才能够将当前页面嵌入到它们的iframe中。
具体来说,“同源”指的是以下三个部分完全一致:
协议:比如都是http或者都是https。
域名:比如都是example.com。
端口:比如都是80或443或其他相同的端口号。
这个策略是由浏览器的安全机制——同源策略(Same-Origin Policy)所强制执行的。它是为了保护用户的安全,防止恶意网站通过iframe加载其他站点的内容来实施各种攻击,如点击劫持(clickjacking)。
当设置了frameOptions(options -> options.sameOrigin())后,如果尝试从不同源(如不同的域名、协议或端口)的页面中加载该页面到iframe内,则浏览器会阻止这种行为,并可能在控制台中记录一个安全错误。这样就确保了只有可信的、同源的页面能够显示该页面的内容。
iframe是什么?
iframe 是 HTML (Hypertext Markup Language) 中的一个标签,全称是 Inline Frame 或者
Inner Frame,即内联框架。它的主要功能是在当前网页中嵌入另一个网页或文档。使用
iframe标签可以将一个独立的HTML文档嵌入到另一个HTML文档中,形成一个“文档中的文档”的效果。 主要用途:
- 在网页中嵌入视频、地图、广告等第三方内容。
- 在一个页面中展示多个不同来源的内容。
- 实现模块化设计,将页面的不同部分分离成单独的文件进行管理。
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
认证失败后进行的处理,下面是ruoyi的处理类实现
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException
{
//http状态码401,表示未授权
int code = HttpStatus.UNAUTHORIZED;
//构建错误信息,包括尝试访问的URL
String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
//使用JSON格式向客户端返回错误信息
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
}
}
// 基于token,所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
- 设置会话管理策略:对Spring Security中的会话管理进行配置。
- 禁用传统会话创建:通过将sessionCreationPolicy设为STATELESS,指示框架不创建标准的HTTP会话。
- 适用于无状态API:这种设置适合不需要维护客户端状态的RESTful服务,减少服务器资源消耗。
- 返回空会话ID:对于每次请求,即使客户端发送了会话ID,服务器也会忽略并视为无状态请求处理。
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
.requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
- 允许所有permitAllUrl设置的路径访问
ruoyi的permitAllUrl配置如下:
/**
* 设置Anonymous注解允许匿名访问的url
*
* @author ruoyi
*/
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{
// 定义一个静态常量Pattern用于匹配格式化字符串中的占位符
// 该占位符格式为{(.*?)},即一对大括号内部可以包含任意字符
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
private ApplicationContext applicationContext;
private List<String> urls = new ArrayList<>();
public String ASTERISK = "*";
@Override
public void afterPropertiesSet()
{
//RequestMappingHandlerMapping负责管理所有带有 @RequestMapping 注解的方法。
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
/**
* 这个 Map 的键 (key) 是 RequestMappingInfo 类型,表示每个 HTTP 请求的映射信息。
* 值 (value) 是 HandlerMethod 类型,表示处理特定请求的具体方法及其所属的控制器类。
* 示例:key = {GET [/monitor/cache/getKeys/{cacheName}]}
* value = com.ruoyi.web.controller.monitor.CacheController#getCacheKeys(String)
**/
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
// 获取方法上边的注解 替代path variable 为 *
Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
// 获取类上边的注解, 替代path variable 为 *
Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
});
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException
{
this.applicationContext = context;
}
public List<String> getUrls()
{
return urls;
}
public void setUrls(List<String> urls)
{
this.urls = urls;
}
}
实现为允许所有配置了Anonymous注解的方法访问
- 对于登录login 注册register 验证码captchaImage 允许匿名访问
- 静态资源,可匿名访问
- 除上面外的所有请求全部需要鉴权认证
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
- logout 方法启动了注销配置流程。
- logoutUrl(“/logout”) 指定了处理注销请求的URL路径为 /logout。
- logoutSuccessHandler(logoutSuccessHandler) 设置了一个自定义的成功注销处理程序,当用户成功注销后,会调用这个处理器来执行后续操作,例如重定向到登录页面或返回成功消息。
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
该方法将自定义的JWT filter认证过滤器 authenticationTokenFilter 添加到 Spring Security 的过滤器链中,并确保其在 UsernamePasswordAuthenticationFilter 之前执行。
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
- .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
将 corsFilter 添加到 JwtAuthenticationTokenFilter 之前。
确保跨域请求处理在 JWT 认证过滤器执行前完成。 - .addFilterBefore(corsFilter, LogoutFilter.class)
再次将 corsFilter 添加到 LogoutFilter 之前。
确保跨域请求处理在登出过滤器执行前完成。



6584

被折叠的 条评论
为什么被折叠?



