当前位置: 首页 > news >正文

权限框架SpringSecurity介绍

权限框架概述

目前市面上比较流行的权限框架主要实Shiro和Spring Security,这两个框架各自侧重点不同,各有各的优劣。

1.1 Apache Shiro

Apache Shiro是一个开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。

特点:

Shiro的特点:

  1. 易于理解的Java Security APl;
  2. 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory等);·
  3. 对角色的简单的签权(访问控制),支持细粒度的签权;
  4. 支持一级缓存,以提升应用程序的性能;
  5. 内置的基于POJO企业会话管理,适用于Web 以及非 Web的环境;
  6. 异构客户端会话访问;
  7. 非常简单的加密API;
  8. 不跟任何的框架或者容器捆绑,可以独立运行。

1.2 SpringSecurity

Spring Security是一个能够为基于Spring的企业应用系统提供描述性安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC(依赖注入,也称控制反转)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security是Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比shiro丰富。一般Web应用的需要进行认证和授权。而认证和授权也是SpringSecurity作为安全框架的核心功能。

SpringSecurity的特点:

  1. 与Spring Boot集成非常简单。
  2. 功能强大,高度可定制化。
  3. 支持OAuth2.0。
  4. 强大的加密API。
  5. 防止跨站请求伪造攻击(CSRF)。
  6. 提供Spring Cloud分布式组件。

1.3 权限框架的选择

Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。
相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,SpringSecurity 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。

Spring Security简介

Spring Security是为基于Spring的应用程序提供声明式安全保护的安全性框架,它提供了完整的安全性解决方案,能够在web请求级别和方法调用级别处理身份证验证和授权。因为基于Spring框架,所以Spring Security充分利用了依赖注入和面向切面的技术。

Spring Security主要是从两个方面解决安全性问题:

  1. web请求级别:使用Servlet规范中的过滤器(Filter)保护Web请求并限制URL级别的访问。
  2. 方法调用级别:使用Spring AOP保护方法调用,确保具有适当权限的用户才能访问安全保护的方法。

依赖如下:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
</dependency>

WebSecurityConfigurerAdapter详解

WebSecurityConfigurerAdapter是Spring Security提供的一个方便的类,用于配置Spring Security的安全性。它提供了一些默认的安全配置,可以通过继承它来自定义安全配置。

WebSecurityConfigurerAdapter类提供了以下方法:

1. configure(HttpSecurity http):用于配置HttpSecurity,即HTTP请求的安全性,自定义URL访问权限的逻辑

2. configure(WebSecurity web):用于配置WebSecurity,即Web请求的安全性,自定义全局安全过滤的逻辑

3. configure(AuthenticationManagerBuilder auth):用于配置AuthenticationManagerBuilder,即认证管理器的安全性,自定义身份认证的逻辑

下面分别介绍这三个方法的作用:

1. configure(HttpSecurity http)

这个是我们使用最多的,该方法用于配置HttpSecurity,即HTTP请求的安全性。通过该方法可以配置Spring Security的许多安全特性,如:

- 配置登录页面和登录请求的URL

- 配置注销页面和注销请求的URL

- 配置访问控制,如哪些URL需要认证、哪些URL需要特定的角色或权限等

- 配置跨站请求伪造(CSRF)保护

- 配置会话管理,如会话超时时间、会话固定攻击保护等

- 配置HTTP Basic认证、HTTP Digest认证、表单认证等

示例代码:

@Configuration 
@EnableWebSecurity 
public class SecurityConfig extends WebSecurityConfigurerAdapter { 
 
    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
        http 
            .authorizeRequests() 
                .antMatchers("/admin/**").hasRole("ADMIN") 
                .antMatchers("/user/**").hasRole("USER") 
                .anyRequest().authenticated() 
                .and() 
            .formLogin() 
                .loginPage("/login") 
                .defaultSuccessUrl("/home") 
                .permitAll() 
                .and() 
            .logout() 
                .logoutUrl("/logout") 
                .logoutSuccessUrl("/login") 
                .permitAll() 
                .and() 
            .csrf().disable(); 
    } 
} 

上面的代码配置了:

*- 访问/admin/*的URL需要ADMIN角色

*- 访问/user/*的URL需要USER角色

- 其他URL需要认证

- 登录页面为/login,登录成功后跳转到/home

- 注销请求的URL为/logout,注销成功后跳转到/login

- 禁用CSRF保护

2. configure(WebSecurity web)

该方法用于配置WebSecurity,即Web请求的安全性。通过该方法可以配置Spring Security的一些Web安全特性,如:

- 配置忽略某些URL的安全性检查

- 配置静态资源的安全性

- 配置HTTP缓存控制

- 配置HTTP响应头

全局过滤

我们一般不会过多来自定义 WebSecurity , 使用较多的使其ignoring() 方法用来忽略 Spring Security 对静态资源的控制。

示例代码:

@Configuration 
@EnableWebSecurity 
public class SecurityConfig extends WebSecurityConfigurerAdapter { 
 
    @Override 
    public void configure(WebSecurity web) throws Exception { 
        web 
            .ignoring() 
                .antMatchers("/resources/**"); 
    } 
} 

*上面的代码配置了忽略/resources/*的URL的安全性检查。

3. configure(AuthenticationManagerBuilder auth)

该方法用于配置AuthenticationManagerBuilder,即认证管理器的安全性。通过该方法可以配置Spring Security的认证特性,如:

- 配置用户认证方式,如内存认证、JDBC认证、LDAP认证等

- 配置密码加密方式

- 配置用户角色和权限

示例代码:

 
@Configuration 
@EnableWebSecurity 
public class SecurityConfig extends WebSecurityConfigurerAdapter { 
 
    @Autowired 
    private UserDetailsService userDetailsService; 
 
    @Autowired 
    private PasswordEncoder passwordEncoder; 
 
    @Override 
    protected void configure(AuthenticationManagerBuilder auth) throws Exception { 
        auth 
            .userDetailsService(userDetailsService) 
            .passwordEncoder(passwordEncoder); 
    } 
} 

上面的代码配置了使用userDetailsService进行用户认证,使用passwordEncoder进行密码加密。其中,userDetailsService和passwordEncoder需要自己实现。

Spring Security中UserDetails相关类

  • UserDetails来表示用户的核心信息:权限集,密码,用户名,账户是否过期,账户是否锁定,凭证是否过期,用户是否可用。在实际开发中,需要扩展UserDetails来自定义存储更多的用户信息。

  • UserDetailsService是Spring Security加载用户数据的核心接口,提供了根据用户名查找用户的方法。
    它包含了一个 loadUserByUsername() 方法,用于根据用户名加载用户信息。开发者可以通过实现 UserDetailsService 接口,并重写 loadUserByUsername() 方法来实现自定义的用户信息加载逻辑。

    public class MyUserDetailsService implements UserDetailsService {
        @Resource
        private PasswordEncoder passwordEncoder;
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        {
            // 这里注入自己的mapper 从数据库中根据用户名加载用户信息
            User user = userMapper.findByUsername(username);
            if (user == null){
                throw new UsernameNotFoundException("用户名不存在");
            }
    		//定义权限列表.
            List<GrantedAuthority> authorities = new ArrayList<>();
            // 用户可以访问的资源名称(或者说用户所拥有的权限) 注意:必须"ROLE_"开头
            authorities.add(new SimpleGrantedAuthority("ROLE_"+ userInfo.getRole()));
            // 返回 UserDetails 对象,用于表示加载的用户信息
            return new org.springframework.security.core.userdetails.User(user.getUsername(), passwordEncoder.encode(userInfo.getPassword()), authorities);
        }
    }
    
  • UserDetailsManager继承自UserDetailsService,并提供了管理用户的方法。

PasswordEncoder密码编码器

PasswordEncoder接口是Spring Security提供的统一密码接口,主要为整个安全框架提供一个统一的加密过程。其主要的实现类如下:

  • DelegatingPasswordEncoder

  • BCryptPasswordEncoder

  • Argon2PasswordEncoder

  • Pbkdf2PasswordEncoder

  • SCryptPasswordEncoder

  • Other PasswordEncoders

除了其它加密算法,以上所列的加密算法都是Spring Security所推荐的,而其它算法主要是为了系统兼容性而存在,但是不再推荐使用。

密码加密简介

1. 散列加密概述

我们开发时进行密码加密,可用的加密手段有很多,比如对称加密、非对称加密、信息摘要等。在一般的项目里,常用的就是信息摘要算法,也可以被称为散列加密函数,或者称为散列算法、哈希函数

这是一种可以从任何数据中创建数字“指纹”的方法,常用的散列函数有 MD5 消息摘要算法、安全散列算法(Secure Hash Algorithm)等

2. 散列加密原理

散列函数通过把消息或数据压缩成摘要信息,使得数据量变小,将数据的格式固定下来,然后将数据打乱混合,再重新创建成一个散列值,从而达到加密的目的。

散列值通常用一个短的随机字母和数字组成的字符串来代表,一个好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理时,如果我们不抑制冲突来区别数据,会使得数据库中的记录很难找到。

但是仅仅使用散列函数还不够,如果我们只是单纯的使用散列函数而不做特殊处理,其实是有风险的!比如在两个用户密码明文相同时,生成的密文也会相同,这样就增加了密码泄漏的风险。

所以为了增加密码的安全性,一般在密码加密过程中还需要“加盐”,而所谓的“盐”可以是一个随机数,也可以是用户名。”加盐“之后,即使密码的明文相同,用户生成的密码密文也不相同,这就可以极大的提高密码的安全性。

**传统的加盐方式需要在数据库中利用专门的字段来记录盐值,**这个字段可以是用户名字段(因为用户名唯一),也可以是一个专门记录盐值的字段,但这样的配置比较繁琐。

3. Spring Security中的密码处理方案

那么在Spring Security中,对密码是怎么进行处理的呢?其实Spring Security对密码的处理方案,有如下3种方式:

  • 对密码进行明文处理,即不采用任何加密方式;
  • 采用MD5加密方式;
  • 采用哈希算法加密方式。

4. BCryptPasswordEncoder简介

spring security中的BCryptPasswordEncoder方法采用SHA-256+随机盐+密钥对密码进行加密。

实际上,Spring Security提供了多种密码加密算法,但官方推荐使用的是BCrypt Password Encoder方案。

我们开发时,用户表中的密码通常是使用MD5等不可逆算法加密后存储,但为了防止破解,可以先使用一个特定的字符串(如域名)进行加密,然后再使用一个随机的salt(盐值)加密。其中特定的字符串是程序代码中固定的,salt是每个密码单独随机的,我们一般会给用户表加一个字段单独存储,但这样比较麻烦。

**而BCrypt算法却可以随机生成salt并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt。**不同于 Shiro 中需要自己处理密码加盐,在 Spring Security 中,BCrypt Password Encoder 本身就自带了盐,所以处理起来非常方便。

代码示例

@Resource
PasswordEncoder passwordEncoder;
//对密码进行加密
user.setPassword(passwordEncoder.encode(user.getPassword()));

配置密码加密算法

接下来我们在Security Config配置类中,配置到底该采用哪种密码加密算法。我们在Spring Boot环境中是非常容易实现加密算法配置的,只需要创建一个Password Encoder对象即可。

//配置采用哪种密码加密算法
@Bean
public PasswordEncoder passwordEncoder() {
    //不使用密码加密
    //return NoOpPasswordEncoder.getInstance();

    //使用默认的BCryptPasswordEncoder加密方案
    return new BCryptPasswordEncoder();

    //strength=10,即密钥的迭代次数(strength取值在4~31之间,默认为10)
    //return new BCryptPasswordEncoder(10);

    //利用工厂类PasswordEncoderFactories实现,工厂类内部采用的是委派密码编码方案.
    //return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

BCryptPasswordEncoder属于第三方依赖

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

HttpSecurity常用方法及说明

方法说明
headers()将安全标头添加到响应
openidLogin()用于基于 OpenId 的验证
cors()配置跨域资源共享( CORS )
sessionManagement()允许配置会话管理
portMapper()允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee()配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
x509()配置基于x509的认证
rememberMe允许配置“记住我”的验证
authorizeRequests()允许基于使用HttpServletRequest限制访问
requestCache()允许配置请求缓存
exceptionHandling()允许配置错误处理
securityContext()在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi()将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf()添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout()添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
anonymous()允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin()指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login()根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel()配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic()配置 Http Basic 验证
addFilterAt()在指定的Filter类的位置添加过滤器

formLogin()表示开启表单登录配置

loginPage用来配置登录页面地址

loginProcessingUrl用来配置登录接口地址;

defaultSuccessUrl表示登录成功后的跳转地址;

failureUrl表示登录失败后的跳转地址;

usernameParameter表示登录用户名的参数名称;

passwordParameter表示登录密码的参数名称;

permitAll表示跟登录相关的页面和接口不做拦截, 直接通过。需要注意的是,loginProcessingUrl、usernameParameter、passwordParameter 需要和login-html中登录表单的配置一致。

authorizeRequests() 访问控制url匹配

1 antMatcher()

规则如下:
? 匹配一个字符
* 匹配0个或多个字符
** 匹配0个或多个目录
在实际项目中经常需要放行所有静态资源,下面演示放行js文件夹下所有脚本文件。
.antMatchers("/js/**").permitAll()
还有一种配置方式是只要是.js文件都放行
antMatchers("/**/*.js").permitAll()

2 anyRequest()

表示匹配所有的请求。一般情况下此方法都会使用,设置全部内容都需要进行认证。
代码示例:
anyRequest().authenticated();

3 regexMatchers()

3.1 介绍
使用正则表达式进行匹配。和antMatchers()主要的区别就是参数,antMatchers()参数是ant表达式,regexMatchers()参数是正则表达式。
演示所有以.js结尾的文件都被放行。
.regexMatchers(".+[.]js").permitAll()
3.2 两个参数时使用方式
无论是antMatchers()还是regexMatchers()都具有两个参数的方法,其中第一个参数都是HttpMethod,表示请求方式,当设置了HttpMethod后表示只有设定的特定的请求方式才执行对应的权限设置。

内置访问控制方法介绍

permitAll()表示所匹配的URL任何人都允许访问。

authenticated()表示所匹配的URL都需要被认证才能访问。

anonymous()表示可以匿名访问匹配的URL。和permitAll()效果类似,只是设置为anonymous()的url会执行filter 链中

denyAll()表示所匹配的URL都不允许被访问。
如果用户未被认证需要认证,如果已经认证,报403

rememberMe() 被“remember me”的用户允许访问

fullyAuthenticated() 如果用户不是被remember me的,才可以访问。

角色权限判断

除了之前讲解的内置权限控制。Spring Security中还支持很多其他权限控制。这些方法一般都用于用户已经被认证后,判断用户是否具有特定的要求。
底层也是调用access(参数)。参数正好是我们调用的方法名。注意如果是判断角色会给调用方法参数前面添加ROLE_,这也是为什么正常调用方法时角色不允许以ROLE_的原因。

hasAuthority(String) 判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建User对象时指定的。

hasAnyAuthority(String …) 如果用户具备给定权限中某一个,就允许访问。

hasRole(String) 如果用户具备给定角色就允许访问。否则出现403。
参数取值来源于自定义登录逻辑UserDetailsService实现类中创建User对象时给User赋予的授权。
在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称。例如:ROLE_abc 其中abc是角色名,ROLE_是固定的字符开头。使用hasRole()时参数也只写abc即可。否则启动报错。

hasAnyRole(String …) 如果用户具备给定角色的任意一个,就允许被访问

hasIpAddress(String) 如果请求是指定的IP就运行访问。
可以通过request.getRemoteAddr()获取ip地址。
需要注意的是在本机进行测试时localhost和127.0.0.1输出的ip地址是不一样的。

自定义403处理方案

  1. 新建类实现AccessDeniedHandler。
@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();
    }
}
  1. 修改配置类
    配置类中重点添加异常处理器。设置访问受限后交给哪个对象进行处理。
    myAccessDeniedHandler是在配置类中进行自动注入的。

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;
    

    在configure()方法中添加下面内容

    //异常处理
    httpSecutity.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
    

基于注解的访问控制

在Spring Security中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过@EnableGlobalMethodSecurity进行开启后使用。
如果设置的条件允许,程序正常执行。如果不允许会报500

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MyApp {
public static void main(String [] args){
SpringApplication.run(MyApp.class,args);
}
}

这些注解可以写到Service接口或方法上上也可以写到Controller或Controller的方法上。通常情况下都是写在控制器方法上的,控制接口URL是否允许被访问。

@Secured

@Secured是专门用于判断是否具有角色的。能写在方法或类上。@Secured参数要以ROLE_开头。

@Secured("ROLE_abc")
@RequestMapping("/toMain")
public String toMain(){
    return "redirect:/main.html";
}
@PreAuthorize

表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解,注解的参数和access()方法参数取值相同,都是权限表达式。

在控制器方法上添加@PreAuthorize,参数可以是任何access()支持的表达式。
如果用户没有管理员角色,不会打印preAuthorize

@RequestMapping("/preAuthorize")
@ResponseBody
@PreAuthorize("hasRole('ROLE_管理员')")
public String preAuthorize(){
    System.out.println("preAuthorize");
    return "preAuthorize";
}
@PostAuthorize

表示方法或类执行结束后判断权限,此注解很少被使用到。

Remember Me功能实现

Spring Security 中Remember Me为“记住我”功能,用户只需要在登录时添加remember-me复选框,取值为true。Spring Security会自动把用户信息存储到数据源中,以后就可以不登录进行访问。

编写配置类

@Configuration
public class RememberMeConfig {
    @Autowired
    private DataSource dataSource;
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl=new JdbcTokenRepositoryImpl();
        jdbcTokenRepositoryImpl.setDataSource(dataSource);
        //自动建表,第一次启动时需要,第二次启动时注释掉
//        jdbcTokenRepositoryImpl.setCreateTableOnStartup(true);
        return jdbcTokenRepositoryImpl;
    }
}

修改SecurityConfig

在SecurityConfig中添加RememberMeConfig和UserDetailsService实现类对象,并自动注入。
在configure中添加下面配置内容。

httpSecurity.rememberMe()
        .userDetailsService(userDetailsService) //登录逻辑交给哪个对象
        .tokenRepository(repository);   //持久层对象

在客户端页面中添加复选框

在客户端登录页面中添加remember-me的复选框,只要用户勾选了复选框下次就不需要进行登录了。

<form action = "/login" method="post">
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="text" name="password"/><br/>
    <input type="checkbox" name="remember-me" value="true"/> <br/>
    <input type="submit" value="登录"/>
</form>

有效时间

默认2周时间。但是可以通过设置状态有效时间,即使项目重新启动下次也可以正常登录。

//remember Me
http.rememberMe()
        .tokenValiditySeconds(120)//单位:秒
        .tokenRepository(repository)
        .userDetailsService(userDetailsServiceImpl);

Thymeleaf中Spring Security的使用

Spring Security可以在一些视图技术中进行控制显示效果。例如:JSP或Thymeleaf。在非前后端分离且使用Spring Boot的项目中多使用Thymeleaf作为视图展示技术。

添加依赖

Thymeleaf对Spring Security的支持都放在thymeleaf-extras-springsecurityX中。所以需要在项目中添加此jar包的依赖和thymeleaf的依赖。

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

在html页面中引入thymeleaf命名空间和security命名空间

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

1 获取属性

可以在html页面中通过 sec:authentication=""获取UsernamePasswordAuthenticationToken中所有getXXX的内容,包含父类中的getXXX的内容。
根据源码得出下面属性:
 name:登录账号名称
 principal:登录主体,在自定义登录逻辑中是UserDetails
 credentials:凭证
 authorities:权限和角色
 details:实际上是WebAuthenticationDetails的实例。可以获取remoteAddress(客户端ip)和sessionId(当前sessionId)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
 <meta charset="UTF-8">
 <title>Title</title>
</head>
<body>
 <!-- springsecurity5标签的使用,是否已授权 -->
<div sec:authorize="isAuthenticated()">
    <p>已登录</p >
    <!-- 获取登录用户名 -->
    <p>登录名:<span sec:authentication="name"></span></p >
    <!-- 获取登录用户所具有的角色、菜单code -->
    <p>角色权限:<span sec:authentication="principal.authorities"></span></p >
    <!-- 获取登录用户名 -->
    <p>Username:<span sec:authentication="principal.username"></span></p >
    <!-- 获取登录的其他属性,比如密码 -->
    <p>Password:<span sec:authentication="principal.password"></span></p >
    
    <p>拥有的角色:
    <!-- 角色判断,是否拥有某个角色,从authorities取值判断 -->
    <span sec:authorize="hasRole('ROLE_ADMIN')">管理员</span>
    <!-- 授权判断,是否拥有某种授权,从authorities取值判断 -->
    <span sec:authorize="hasAnyAuthority('ROLE_ADMINISTRATOR')" >超级管理员</span>
    <!-- 授权判断,是否拥有某种授权,从authorities取值判断 -->
    <span sec:authorize="hasPermission('index','read')" >bbb</span>   
    <!-- 授权判断,是否拥有某种授权,从authorities取值判断 --> 
    <span sec:authorize="hasAnyAuthority('index:write')" >eeee</span>
    </p >
</div>
</body>
</html>

2 权限判断

在html页面中可以使用sec:authorize=”表达式”进行权限控制,判断是否显示某些内容。表达式的内容和access(表达式)的用法相同。如果用户具有指定的权限,则显示对应的内容;如果表达式不成立,则不显示对应的元素。

3. 退出登录

用户只需要向Spring Security项目中发送/logout退出请求即可。

<a href="/logout">退出登录</a>

如果不希望使用默认值,可以通过下面的方法进行修改。

httpSecurity.logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login.html");

Spring Security中CSRF

从刚开始学习Spring Security时,在配置类中一直存在这样一行代码:httpSecurity.csrf().disable();如果没有这行代码导致用户无法被认证。
这行代码的含义是:关闭csrf防护。

1 什么是CSRF

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack” 或者Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。
跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。
客户端与服务进行交互时,由于http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持,通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。

一个典型的CSRF攻击有着如下的流程:

  1. 受害者登录a.com,并保留了登录凭证(Cookie)。
  2. 攻击者引诱受害者访问了b.com。
  3. b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie。
  4. a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
  5. a.com以受害者的名义执行了act=xx。
  6. 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。

CSRF的特点:

  • 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
  • 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
  • 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。
  • 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。

2 Spring Security中CSRF

从Spring Security4开始CSRF防护默认开启。默认会拦截请求。进行CSRF处理。CSRF为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf值为token(token在服务端产生)的内容,如果token和服务端的token匹配成功,则正常访问。

实现步骤

2.1 编写控制器方法,跳转到templates中login.html页面

@GetMapping("/showLogin")
public String showLogin() {
    return "login";
}

2.2 在项目resources下新建templates文件夹,并在文件夹中新建login.html页面。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action = "/login" method="post">
    <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    <input type="submit" value="登录"/>
</form>
</body>
</html>
Spring Security中CSRF原理
  1. 当服务器加载登录页面。(loginPage中的值,默认/login),先生成csrf对象,并放入作用域中,key为_csrf。之后会对${_csrf.token}进行替换,替换成服务器生成的token字符串。
  2. 用户在提交登录表单时,会携带csrf的token。如果客户端的token和服务器的token匹配说明是自己的客户端,否则无法继续执行。

Spring Security异常体系

Spirng Security中的异常共有两大类:AuthenticationException(认证异常)和AccessDeniedException(权限异常)

AuthenticationException(认证异常)

AuthenticationException:认证异常的父类,抽象类
BadCredentialsException:登录凭证(密码)异常
InsufficientAuthenticationException:登陆凭证不够充分而抛出的异常
SessionAuthenticationException:会话并发管理时抛出的异常,例如会话总数超出最大限制数
UsernameNotFoundException:用户名不存在异常
PreAuthenticatedCredentialsNotFoundException:身份预认证失败异常
ProviderNotFoundException:未配置AuthenticationProvider异常
AuthenticationServiceException:由于系统问题而无法处理认证请求异常
InternalAuthenticationServiceException:由于系统问题而无法处理认证请求异常,和AuthenticationServiceException不同之处在于如果外部系统出错,不会抛出该异常
AuthenticationCredentialsNotFoundException:SecuityContext 中不存在认证主体时抛出的异常
NonceExpiredException:HTTP摘要认证时随机数过期异常
RememberMeAuthenticationException:RememberMe认证异常
CookieTheftException :RememberMe认证时Cookie被盗窃异常
InvalidCookieException:RememberMe认证时无效的Cookie异常
AccountStatusException:账户状态异常
LockedException:账户被锁定异常
DisabledException:账户被禁用异常
CredentialsExpiredException:登录凭证(密码)过期异常
AccountExpiredException:账户过期异常

AccessDeniedException(权限异常)

AccessDeniedException :权限异常的父类
AuthorizationServiceException: 由于系统问题而无法处理权限时抛出异常
CsrfException:Csrf令牌异常
MissingCsrfTokenException:Csrf令牌缺失异常
InvalidCsrfTokenException:Csrf令牌无效异常

在实际项目中,如果Spring Security提供的这些异常类无法满足需要,开发者也可以根据实际需要自定义异常类

解决无法捕捉UsernameNotFoundException

使用spring security进行授权登录的时候,发现登录接口无法正常捕捉UsernameNotFoundException异常,捕捉到的一直是BadCredentialsException异常。我们的预期是:

  • UsernameNotFoundException -> 用户名错误
  • BadCredentialsException -> 密码错误
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(dap());
}
@Bean
public DaoAuthenticationProvider dap() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setPasswordEncoder();
    provider.setUserDetailsService();
    provider.setHideUserNotFoundExceptions(false);
    return provider;
}

SpringSecurity:session管理

1 Session超时

当用户登录后,我们可以设置 session 的超时时间,当达到超时时间后,自动将用户退出登录。

Session 超时的配置是 SpringBoot 原生支持的,我们只需要在 application.properties 配置文件中配置:

server:
  servlet:
    session:
      timeout: 60  # 过期时间,单位s

Spring Security 提供了两种过期后处理配置,一个是 invalidSessionStrategy(),另外一个是 invalidSessionUrl()

这两个的区别就是一个是前者是在一个类中进行处理,后者是直接跳转到一个 Url。

2 限制最大登录数

原理:限制单个用户能够存在的最大session数

http.sessionManagement()下添加三行代码:

  • maximumSessions(int):指定最大登录数
  • maxSessionsPreventsLogin(boolean):是否保留已经登录的用户;为true,新用户无法登录;为 false,旧用户被踢出
  • expiredSessionStrategy(SessionInformationExpiredStrategy):旧用户被踢出后处理方法

http.sessionManagement().maximumSessions(1);意思是开启session管理,session并发最多一个,超出后,旧的session被注销,新的会注册,这种操作称为缺省实现 。

session缺省实现原理是将session记录在内存map中,因此不能用于集群环境中,会导致服务器1中记录的信息和服务器2记录的信息并不相同;

解决的方案是使用spring session ,session存在redis里面作为共享信息

示例

http.sessionManagement()
	// 会话过期后 需要跳转的地址
    .invalidSessionUrl("/login/invalid")
    // 一个账户最多允许一个会话
    .maximumSessions(1)
    //当达到最大值时,是否保留已经登录的用户 false为将上一个用户挤掉
    .maxSessionsPreventsLogin(false)
    //当达到最大值时,旧用户被提出后的操作
    .expiredSessionStrategy(new MyExpiredSessionStrategy());

编写 MyExpiredSessionStrategy 类,来处理旧用户登陆失败的逻辑:

public class MyExpiredSessionStrategy implements SessionInformationExpiredStrategy {
   //    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        // 1. 获取用户名
        UserDetails userDetails = (UserDetails)event.getSessionInformation().getPrincipal();
        Map<String, Object> map = new HashMap<>();
        map.put("code", 0);
        map.put("msg", userDetails.getUsername()+"已经另一台机器登录,您被迫下线。");
        // Map -> Json
        String json = JSON.toJSONString(map);

        event.getResponse().setContentType("application/json;charset=UTF-8");
        event.getResponse().getWriter().write(json);

        // 如果是跳转html页面,url代表跳转的地址
        // redirectStrategy.sendRedirect(event.getRequest(), event.getResponse(), "url");
    }
}

解决中文乱码问题

CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding(“UTF-8”);
filter.setForceEncoding(true);
http.addFilterBefore(filter,CsrfFilter.class);

相关文章:

  • 【功能开发】DSP F2837x 检测中断所有函数运行一次的时间
  • 多模态大语言模型arxiv论文略读(二)
  • 基于Edge-TTS的OpenAI兼容文本转语音API实战指南
  • QwQ-32B-GGUF模型部署
  • 快速入手-基于DRF的过滤、分页、查询配置(十五)
  • 2025年渗透测试面试题总结-某 携程旅游-基础安全工程师(题目+回答)
  • 41、当你在 index.html 中引用了一个公共文件(比如 common.js),修改这个文件后,用户访问页面时仍然看到旧内容,因为浏览器缓存了旧版本
  • 人工智能-LangGraph+ChatUI+DeepSeek API搭建本地智能助手
  • 搭建开源笔记平台:outline
  • 如何在 Unity3D 导入 Spine 动画
  • 【NLP】15. NLP推理方法详解 --- 动态规划:序列标注,语法解析,共同指代
  • JavaScript 库:全面解析与推荐
  • 13-SpringBoot3入门-整合MyBatis-Plus
  • 【Docker镜像】Python项目之使用Dockerfile构建镜像(一)
  • 如何用Postman实现自动化测试?
  • 【Jira 查询与 JQL 使用详解】
  • 【数据结构】导航
  • Ubuntu 24.04.2 LTS 系统安装python,创建虚拟环境
  • 解决 CMS Old GC 频繁触发线上问题技术方案
  • 本地部署vanna ai+通过http请求调用vanna
  • 5月12日至13日北京禁飞“低慢小”航空器
  • 上海“随申兑”服务平台有哪些功能?已归集800余个惠企政策
  • 美联储连续第三次维持利率不变,警示关税影响
  • 印巴局势快速升级,外交部:呼吁印巴以和平稳定的大局为重
  • 8小时《大师与玛格丽特》:长度可以是特点,但不是价值标准
  • 李云泽:小微企业融资协调工作机制已发放贷款12.6万亿元