目录
- 认证
- 内存认证
- 自定义认证逻辑
- 密码解析器PasswordEncorder
- 不使用PasswordEncorder
- 其他密码解析器
- 自定义登录认证(资源权限配置规则)
- 安全过滤链SecurityFilterChain
- 自定义成功处理器
- 自定义认证失败处理器
- 退出登录
- 退出成功处理器
- 记住账号、密码
- 会话管理
- 会话管理的关键类
- SessionInformation(会话信息的实体类)
- SessionRegistry(会话信息)
- SecurityContext(封装用户信息)
- 会话失效的处理
- 会话并发控制
- 主动会话失效(踢人)
认证
内存认证
描述:简单而快速的认证方式,它将用户的身份信息存储在内存中,而不是使用数据库或其他持久化存储。(一般仅用于生产开发)
示例
package com.wunaiieq.tmp2024121105.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;//security的配置类
@Configuration
public class SecurityConfig {@Beanpublic UserDetailsService userDetailsService(){//1.内存数据认证InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();//2.创建用户UserDetails user1 = User.withUsername("wunaiieq").password("123456").authorities("admin").build();//3.添加到内存inMemoryUserDetailsManager.createUser(user1);return inMemoryUserDetailsManager;}//密码默认加密,此处配置密码编码器,设置不解析密码@Beanpublic PasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();}
}
自定义认证逻辑
描述:重写UserDetailsService接口内部的方法即可
注意:一个项目中只能有一个认证逻辑
示例
逻辑认证中,我们一般需要从数据库中获取相应的数据内容,会调用mapper层中的方法,所以这个功能多处于service层中
以下为service层的方法演示
完整代码
package com.wunaiieq.tmp2024121105.service;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wunaiieq.tmp2024121105.entity.Users;
import com.wunaiieq.tmp2024121105.mapper.UsersMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;@Service
public class UserDetailService01 implements UserDetailsService {@Autowiredprivate UsersMapper usersMapper;//自定义认证逻辑@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//1.查询条件QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username", username);//2.查询用户(这里的Users是自定义的实体类 )Users users =usersMapper.selectOne(wrapper);//3.封装为UserDetails对象UserDetails userDetails = User.withUsername(users.getUsername()).password(users.getPassword()).authorities("admin").build();//4.返回封装好的UserDetails对象return userDetails;}
}
密码解析器PasswordEncorder
在security中必须设置PasswordEncorder实例,对密码进行加密
不使用PasswordEncorder
创建security的配置类,设置NoOpPasswordEncoder
package com.wunaiieq.tmp2024121105.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
//security的配置类
@Configuration
public class SecurityConfig {//密码默认加密,此处配置密码编码器,设置不解析密码@Beanpublic PasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();}
}
其他密码解析器
密码解析器名称 | 简要描述 | 安全性评价 | 示例代码 |
---|---|---|---|
Argon2PasswordEncoder | 基于Argon2算法,内存硬化,适用于防御侧信道攻击。 | 高 | Argon2PasswordEncoder encoder = new Argon2PasswordEncoder(); |
BCryptPasswordEncoder | 基于Blowfish算法,通过增加计算复杂度抵御暴力破解。 | 中高 | BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); |
DelegatingPasswordEncoder | 可委托给不同编码器,根据前缀选择具体编码器,灵活性强。 | 依赖于委托的编码器 | DelegatingPasswordEncoder encoder = new DelegatingPasswordEncoder("bcrypt"); |
LazyPasswordEncoder | 延迟编码过程,常用于配置阶段不立即编码的场景。 | 依赖于实际使用的编码器 | 在AuthenticationConfiguration 或WebSecurityConfigurerAdapter 中配置。 |
LdapShaPasswordEncoder | 基于LDAP的SHA密码编码,适用于LDAP认证。 | 中 | LdapShaPasswordEncoder encoder = new LdapShaPasswordEncoder(); |
MessageDigestPasswordEncoder | 使用MessageDigest 类,支持多种算法,但安全性较低。 | 低(不推荐) | MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder("SHA-256"); |
NoopPasswordEncoder | 不进行编码,直接返回原始密码,安全性极低。 | 极低(不推荐) | NoopPasswordEncoder encoder = NoopPasswordEncoder.getInstance(); |
Pbkdf2PasswordEncoder | 基于PBKDF2算法,通过迭代次数和盐值提高安全性。 | 中高 | Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder(); |
SCryptPasswordEncoder | 基于scrypt算法,内存硬化,适用于高安全性要求。 | 高 | SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); |
StandardPasswordEncoder | 使用SHA-256和固定盐值,安全性较低(不推荐)。 | 低(不推荐) | StandardPasswordEncoder encoder = new StandardPasswordEncoder(); |
UnmappedIdPasswordEncoder | 处理未映射ID的编码,通常与DelegatingPasswordEncoder 结合。 | 依赖于DelegatingPasswordEncoder | 作为DelegatingPasswordEncoder 的一部分,不单独使用。 |
示例
以常用的BCryptPasswordEncoder为例
在Security的配置类中编写如下代码(此处不包含用户注册,也就是说,你需要提前在数据库中输入加密后的密码)
SecurityConfig
package com.wunaiieq.tmp2024121105.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;//security的配置类
@Configuration
public class SecurityConfig {//密码默认加密,此处配置密码编码器,设置不解析密码@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}
加密密码的测试类
@Testpublic void Password_Encoder() {//BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();//String password1 = bCryptPasswordEncoder.encode("wunaiieq");Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder();String password3=pbkdf2PasswordEncoder.encode("wunaiieq");System.out.println(password3);}
自定义登录认证(资源权限配置规则)
安全过滤链SecurityFilterChain
在类上加上注解——@EnableWebSecurity表示启用web功能
描述:SecurityFilterChain是Spring Security框架中的一个核心接口,用于定义安全过滤器链的行为。在Spring Security中,每个HTTP请求都会经过一个或多个安全过滤器,这些过滤器负责执行各种安全操作,如身份验证、授权、CSRF防护等。SecurityFilterChain接口及其实现类提供了管理和执行这些安全过滤器的方式。
HttpSecurity:HttpSecurity 是用于构建 SecurityFilterChain 的主要工具。它提供了一系列方法,用于配置各种安全策略。
示例
package com.wunaiieq.tmp2024121105.config;import com.wunaiieq.tmp2024121105.handler.MyLoginSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;//security的配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{httpSecurity.formLogin(form->{form.loginPage("/login") //指定登录页的路径,默认/login.usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login") //指定自定义form表单请求的路径(必须跟login.html中的form action=“url”一致)
// .successForwardUrl("/index.html") //认证登录成功后跳转的路径.successHandler(new MyLoginSuccessHandler())//登录成功处理器.failureForwardUrl("/fail.html");});httpSecurity.authorizeHttpRequests(resp->{resp.regexMatchers("/login.html").permitAll();resp.anyRequest().authenticated();});httpSecurity.csrf(csrf->{csrf.disable();//关闭csrf防护});return httpSecurity.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}
自定义成功处理器
描述:在登录成功后,进行相应的操作。这些操作定义在认证成功处理器中
示例:
认证成功处理器定义
package com.wunaiieq.tmp2024121105.handler;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException, IOException {// 拿到登录用户的信息UserDetails userDetails = (UserDetails)authentication.getPrincipal();System.out.println("用户名:"+userDetails.getUsername());System.out.println("一些操作...");// 重定向到主页response.sendRedirect("/main");}
}
调用方式
在安全链中调用,
.successHandler(new MyLoginSuccessHandler())//认证成功处理器
package com.wunaiieq.tmp2024121105.config;import com.wunaiieq.tmp2024121105.handler.MyLoginSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;//security的配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{httpSecurity.formLogin(form->{form.loginPage("/login") //指定登录页的路径,默认/login.usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login") //指定自定义form表单请求的路径(必须跟login.html中的form action=“url”一致)
// .successForwardUrl("/index.html") //登录成功后跳转的路径.successHandler(new MyLoginSuccessHandler())//认证成功处理器.failureForwardUrl("/fail.html");});httpSecurity.authorizeHttpRequests(resp->{resp.regexMatchers("/login.html").permitAll();resp.anyRequest().authenticated();});httpSecurity.csrf(csrf->{csrf.disable();});return httpSecurity.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}
自定义认证失败处理器
描述:在登录失败后,进行相应的操作。这些操作定义在认证失败处理器中
示例:
认证失败处理器定义
package com.wunaiieq.tmp2024121105.handler;import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class MyLoginFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {System.out.println("登录失败");response.sendRedirect("/fail.html");}
}
退出登录
描述:
1.清除认证状态
2.销毁HttpSession对象(java原生对象)
3.跳转到登录页面
示例
需要嵌入到SecurityFilterChain链中
httpSecurity.logout(logout->{logout.logoutUrl("/logout")//退出登录路径.logoutSuccessUrl("/login.html")//退出登录后的跳转路径.clearAuthentication(true)//清楚认证状态,默认值为true.invalidateHttpSession(true);//销毁httpSession对象,默认true});
退出成功处理器
描述:在退出登录后,进行相应的操作。这些操作定义在认证退出成功处理器中
退出成功处理器示例
package com.wunaiieq.tmp2024121105.handler;import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {System.out.println("清除一些数据...");response.sendRedirect("/login.html");}
}
嵌入到SecurityFilterChain链中
httpSecurity.logout(logout->{logout.logoutUrl("/logout")//退出登录路径//.logoutSuccessUrl("/login.html")//退出登录后的跳转路径.logoutSuccessHandler(new MyLogoutSuccessHandler())//自定义退出登录处理器.clearAuthentication(true)//清楚认证状态,默认值为true.invalidateHttpSession(true);//销毁httpSession对象,默认true});
记住账号、密码
描述:Spring Security中Remember Me为“记住我”功能,即下次访问系统时无需重新登录。当使用“记住我”功能登录后,Spring Security会生成一个令牌,令牌一方面保存到数据库中,另一方面生成一个叫remember-me的Cookie保存到客户端。之后客户端访问项目时自动携带令牌,不登录即可完成认证
配置示例
这里的自动建表,会建立在application.yml中配置的datasource下
package com.wunaiieq.tmp2024121105.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;import javax.sql.DataSource;@Configuration
public class RememberMeConfig {@Autowiredprivate DataSource dataSource;// 令牌Repository@Beanpublic PersistentTokenRepository getPersistentTokenRepository() {// 为Spring Security自带的令牌控制器设置数据源JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();jdbcTokenRepositoryImpl.setDataSource(dataSource);//自动建表,第一次启动时需要,第二次启动时注释掉//jdbcTokenRepositoryImpl.setCreateTableOnStartup(true);return jdbcTokenRepositoryImpl;}
}
嵌入到SecurityFilterChain链中
httpSecurity.rememberMe(remeberme->{remeberme.userDetailsService(userDetailService01)//认证逻辑对象.tokenRepository(repository)//持久层对象.tokenValiditySeconds(300);//数据保存时间,单位:秒});
前端表单
这里的记住我复选框,name必须写"remember-me"
<form class="form" action="/login" method="post"><input type="text" placeholder="用户名" name="username"><input type="password" placeholder="密码" name="password"><input type="checkbox" name="remember-me" value="true"/>记住我</br><button type="submit">登录</button>
</form>
会话管理
描述:会话是指用户登录成功后,系统和用户之间建立的一种连接状态。通过会话,系统能够识别出访问请求来自哪个用户,并据此提供个性化的服务或限制资源的访问。
作用:
- 用户认证:用户认证通过后,为了避免每次操作都进行认证,可以将用户的信息保存在会话中。这样,用户后续的请求只需携带会话标识(如Session ID或Token),系统即可验证用户的身份。
- 状态维持:HTTP协议本身是无状态的,即每个请求都是独立的,没有上下文关系。会话机制通过在服务器端存储用户的状态信息,使得用户的多次请求之间能够保持连续性。
会话管理的关键类
SessionInformation(会话信息的实体类)
描述:会话信息的实体类,记载相应的会话信息,没什么特别需要注明的。
SessionRegistry(会话信息)
描述:
- 维护会话信息:SessionRegistry维护了一个SessionInformation实例的注册表,这些实例包含了关于会话的详细信息,如会话ID、关联的用户主体、会话创建和最后访问时间等。
- 会话管理:通过SessionRegistry,可以获取当前所有活动的会话信息,以及针对特定用户的会话信息。此外,它还可以用于注册新的会话、刷新会话的最后访问时间、移除会话信息等。
示例
在使用之前,需要在security的配置类中,完成bean注册,如下
@Beanpublic SessionRegistry sessionRegistry(){return new SessionRegistryImpl();}
调用
关于调用,具体的例子可以看主动踢人这个小章(文末)
自动装配下,拿来用就行
SecurityContext(封装用户信息)
描述:封装了关于当前用户与安全性相关的信息,包括用户的认证状态、身份信息(如用户名、密码、角色等)以及相关的权限(GrantedAuthority集合)。
核心组件:Authentication用户认证信息
示例
package com.wunaiieq.tmp2024121105.controller;import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class MyController {// 获取当前登录用户名@RequestMapping("/users/username")public String getUsername(){// 1.获取会话对象SecurityContext context = SecurityContextHolder.getContext();// 2.获取认证对象Authentication authentication = context.getAuthentication();// 3.获取登录用户信息UserDetails userDetails = (UserDetails) authentication.getPrincipal();return userDetails.getUsername();}
}
会话失效的处理
描述:会话失效是指用户与服务器之间建立的会话(session)数据被销毁。此时,客户端需要重新登录或重新建立会话,以继续与服务器进行交互。
原因:原因很多:会话超时,服务器宕机,网络中断,内存不足等等
以会话超时为例子
超时设置
在application.yml中进行如下配置,表示60秒会会话超时
server:port: 80servlet:session:timeout: 60s
会话无效配置
描述:实现InvalidSessionStrategy接口中的方法,完成当会话无效后的处理
值得说明的是,在会话过期后,重定向到指定页面(比如登录界面)之前,应当创建新的会话,否则,会产生没有会话->重定向->没有会话->重定向。。。的奇观。
请注意,会话过期,超时,只是会话无效中的一种
自定义测到无效会话(如会话过期)时的策略类
package com.wunaiieq.tmp2024121105.handler;import org.springframework.security.web.session.InvalidSessionStrategy;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class MyInvalidSessionStrategy implements InvalidSessionStrategy {@Overridepublic void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {System.out.println("会话过期");// 会话失效,需要创建新session,否则会由于一直没有session不断的重定向request.getSession();response.sendRedirect("/login");}
}
加入到处理链中
httpSecurity.sessionManagement(session->{//会话过期跳转的页面,默认的会话过期处理跳转//session.invalidSessionUrl("/login");session.invalidSessionStrategy(new MyInvalidSessionStrategy());});
会话并发控制
描述:控制某个账号同一时间仅允许在一个终端上登录,比如QQ,当一个用户已经在某个设备上登录时,如果该用户尝试在另一个设备上登录,系统会自动踢掉前面的登录会话,以确保只有一个会话处于活动状态。
踢掉原有的登录用户
在处理链中加入修改即可
1表示最大并发会话数即允许用户同时在线的最大会话数,新登录的会替代最老的
httpSecurity.sessionManagement(session->{//会话过期跳转的页面,默认的会话过期处理跳转//session.invalidSessionUrl("/login");session.invalidSessionStrategy(new MyInvalidSessionStrategy());//最大并发会话数,数量表示允许用户同时在线的最大会话数,新登录的会替代最老的session.maximumSessions(1);});
阻止新用户登录
在处理链中加入修改即可
在session.maximumSessions(1)后增加.maxSessionsPreventsLogin(true)
其表示当会话数达到设定的最大值时,阻止创建新的会话,保留原有的会话(默认值为false)
也就是组织新用户登录
httpSecurity.sessionManagement(session->{//会话过期跳转的页面,默认的会话过期处理跳转//session.invalidSessionUrl("/login");session.invalidSessionStrategy(new MyInvalidSessionStrategy());//最大并发会话数,数量表示允许用户同时在线的最大会话数,新登录的会替代最老的//maxSessionsPreventsLogin(true)表示当会话数达到设定的最大值时,阻止创建新的会话,保留原有的会话(默认值为false)session.maximumSessions(1).maxSessionsPreventsLogin(true);});
主动会话失效(踢人)
使用SessionRegistry,查询所有的登录用户,并使指定用户的会话失效
package com.wunaiieq.tmp2024121105.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class SessionController {@Autowiredprivate SessionRegistry sessionRegistry;// 踢出指定用户@GetMapping("/kickOut")public void kickOutUser(String username) {// 1.获取全部登录用户List<Object> allPrincipals = sessionRegistry.getAllPrincipals();// 2.遍历全部登录用户,找到要强制登出的用户for (Object principal : allPrincipals) {UserDetails userDetail = (UserDetails) principal;if (username.equals(userDetail.getUsername())) {// 3.找到认证用户所有的会话,不包含过期会话List<SessionInformation> sessions = sessionRegistry.getAllSessions(userDetail, false);if (null != sessions && !sessions.isEmpty()) {// 4.遍历该用户的会话,使其立即失效for (SessionInformation session : sessions) {session.expireNow();}}}}}}