目录
1. 直接注入 Principal
2. 直接注入 Authentication
3. 注入 UsernamePasswordAuthenticationToken
4. 通过 SecurityContextHolder 获取
5. 使用自定义工具方法
总结
如何获取更多的用户信息
自定义用户实体类
如何忽略某些字段(不返回前端)以及规范日期格式
登录成功后的处理:
在 Spring Security 中,当前登录用户的信息实际上都存储在与当前线程绑定的 SecurityContext 中,里面保存了一个 Authentication 对象。你展示的这几种写法,其实都是不同的方式来获取这个 Authentication 对象或者从中提取出用户信息。下面详细说明每种方式的原理和适用场景:
1. 直接注入 Principal
@GetMapping("/welcome")
@ResponseBody
public Object toLoginInfo(Principal principal){return principal;
}
说明:
当你在 Controller 方法中直接声明一个 Principal
类型的参数时,Spring Security 会自动注入当前登录用户对应的 Principal
对象。通常,Principal
只包含最基本的用户标识(例如用户名),但如果需要更多信息,就需要使用 Authentication
。
2. 直接注入 Authentication
@GetMapping("/welcome2")
@ResponseBody
public Object toLoginInfo2(Authentication authentication) {return authentication;
}
说明:
Authentication
接口继承自 Principal
,除了包含用户名外,还包含了用户的权限信息(Authorities)、认证凭证(Credentials)、认证状态等。直接注入 Authentication
可以获取更丰富的信息,是实际开发中常用的方式之一。
3. 注入 UsernamePasswordAuthenticationToken
@GetMapping("/welcome3")
@ResponseBody
public Object toLoginInfo3(UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) {return usernamePasswordAuthenticationToken;
}
- 说明:
UsernamePasswordAuthenticationToken
是Authentication
的一个常见实现,通常在基于表单登录时使用。它除了保存用户名和密码,还保存了用户权限等信息。直接注入这种类型的参数,与直接注入Authentication
类似,只是类型更加具体。
4. 通过 SecurityContextHolder
获取
@GetMapping("/welcome4")
@ResponseBody
public Object toLoginInfo4() {return SecurityContextHolder.getContext().getAuthentication();
}
说明:
这种方式是从静态的 SecurityContextHolder
中获取当前线程绑定的 SecurityContext
,再从中取出 Authentication
对象。使用这种方式可以在非 Controller 的地方(例如在业务逻辑或工具类中)获取当前登录用户的信息。
5. 使用自定义工具方法
@GetMapping("/welcome5")
@ResponseBody
public Object toLoginInfo5() {return LoginInfoUtil.getCurrentLoginUser();
}
说明:
这是一个对上面几种方式的封装,通常会在项目中封装一个工具类(如 LoginInfoUtil
),内部封装对 SecurityContextHolder
的调用或者其他逻辑处理,返回一个更为友好的用户信息对象。这样做有利于集中管理和统一用户信息的获取逻辑。
LoginInfoUtil:
public class LoginInfoUtil {/*** 获取当前登录用户信息* @return*/public static TUser getCurrentLoginUser() {return (TUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();}
}
总结
-
统一存储:
Spring Security 将当前认证信息统一保存在SecurityContext
中,通过SecurityContextHolder
实现线程级别的绑定。 -
多种方式:
你可以直接通过方法参数(如Principal
、Authentication
或更具体的认证类)来获取,也可以在任意位置通过SecurityContextHolder.getContext().getAuthentication()
获取。这些方式本质上都是获取同一个对象,只是使用方式不同。 -
扩展与封装:
如果在项目中经常需要获取当前用户信息,可以封装一个工具类,方便调用和后续扩展。
Authentication
继承自 Principal
,这意味着它不仅包含基本的身份信息,还扩展了额外的认证细节。
UsernamePasswordAuthenticationToken
是 Authentication
接口的一个具体实现,专门用于用户名和密码认证的场景。
在实际认证流程中,通常会通过 UsernamePasswordAuthenticationToken
来封装用户提交的认证信息,并在认证成功后返回一个完整的认证对象。
如何获取更多的用户信息
实体类实现UserDetails接口,实现UserDetails的7个接口,这样,你既可以保留 Spring Security 要求的七个方法,又可以在实体类中扩展其他的业务属性。
自定义用户实体类
扩展业务属性:
在你的用户实体类中,可以添加额外的属性,例如真实姓名、邮箱、电话等等。
实现 UserDetails 接口的七个方法:
-
getAuthorities():
返回用户拥有的权限(或角色),通常是一个包含 GrantedAuthority 对象的集合。 -
getPassword():
返回用户的密码,用于身份验证。 -
getUsername():
返回用户名。 -
isAccountNonExpired():
表示用户账户是否未过期。返回 true 表示账户有效,未过期。 -
isAccountNonLocked():
表示用户账户是否未被锁定。返回 true 表示账户未被锁定。 -
isCredentialsNonExpired():
表示用户的凭证(密码)是否未过期。 -
isEnabled():
表示用户账户是否可用(启用状态)。
之后由于之前是返回UserDetails的实现类User,此时由于我们有自定义的实现类TUser,因此我们可以直接返回了,不需要使用框架的User类了。
@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 通过用户名查询数据库TUser user = userMapper.selectByLoginAct(username);if (user == null){throw new UsernameNotFoundException("用户不存在");}return user; // 实现了UserDetails接口,包含所有字段// // 返回框架User(UserDetails的实现类)但是使用UserDetails接收
// return User.builder()
// .username(user.getLoginAct())
// .password(user.getLoginPwd())
// .authorities(AuthorityUtils.NO_AUTHORITIES) // 没有权限(权限管理部分)
// .build(); // 把UserDetails(User)返回给框架之后,框架会采用密码加密器进行密码的比较}
如何忽略某些字段(不返回前端)以及规范日期格式
使用
@JsonIgnore // 表示该字段不返回给前端
如果实体类当中日期类较多,每一个字段加上规范会很麻烦,因此一般会在application.yml当中配置Jackson的转换方式:
spring:application:name: Security-04-login-info# jackson配置 时间格式(使用指定的时区和格式)jackson:time-zone: GMT+8date-format: yyyy-MM-dd HH:mm:ss
登录成功后的处理:
@Bean// 安全过滤器链Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { //httpSecurity方法参数注入Beanreturn httpSecurity// 配置自己的登录页面.formLogin( (formLogin) ->{formLogin.loginProcessingUrl("/login") // 登录账户密码往哪个地址提交.loginPage("/toLogin")// 定制登录页面.successForwardUrl("/welcome"); // 登录成功之后,跳转到哪个页面,默认是跳转到之前的页面}).authorizeHttpRequests((authorizeHttpRequests)->{authorizeHttpRequests.requestMatchers("/toLogin","/common/captcha").permitAll() // 特殊情况,toLogin不需要登录就可以访问, 验证码不需要登录就可以访问.anyRequest().authenticated(); // 除了上述特殊情况之外,其他任何请求都需要认证之后才能访问}).addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class).build();}
使用 .successForwardUrl("/welcome") 时,Spring Security 在用户登录成功后不会发起新的 HTTP 请求,而是通过内部的请求转发(forward)的方式,将当前请求转发到指定的 URL(即 "/welcome")。这意味着:
- 内部转发: 请求在服务器内部进行转发,不会改变浏览器地址栏显示的 URL。
- 请求方法保持不变: 转发过程中,原始的 HTTP 请求方法(例如 POST)会被保留。因此,如果登录请求是 POST,则转发后的 "/welcome" 接口也是 POST 请求。
- 常用于登录成功后的处理: 这种方式适合需要在登录成功后继续处理原始请求数据或者保持请求上下文的场景。
总结来说,.successForwardUrl("/welcome") 用于在认证成功后,将请求内部转发到 "/welcome" 接口,从而执行相应的处理逻辑,而不是向浏览器发起新的请求。