是一个功能强大且高度可定制的身份验证和访问控制框架。
是一个专注于为Java应用程序提供身份验证和授权的框架。
是一个能够为基于Spring的企业应用系统提供声明式(注解)的安全访问控制解决方案的安全框架。
它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI和AOP功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
一句话来概括,SpringSecurity 是一个安全框架。
官网: https://spring.io/projects/spring-security
中文文档: https://www.springcloud.cc/spring-security.html
声明式权限鉴定框架
了解一下:还有一个声明式权限鉴定框架:shiro
使用的时候就是:过滤器+注解
RBAC(Role-Based Access Control):基于角色的访问控制
if(主体.hasRole("总经理角色id")){
查询工资
}
RBAC(Resource-Based Access Control):基于资源(或权限)的访问控制
if(主体.hasPermission("查询工资") ){
查询工资
}
前者可扩展性差,后者可扩展性强
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-testartifactId>
<scope>testscope>
dependency>
#如果使用了配置类,这里就失效了
spring:
security:
user:
name: admin
password: 123
/**
* SpringSecurity的配置类
* 这里配置了,配置文件里的就失效了
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置Security登录的用户信息
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中配置用户
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123"))
//配置角色
//如果为用户配置的是角色,那么系统会在角色字符串前追加一个ROLE_的前缀,表示当前是角色
.roles("ADMIN")
.and()
.withUser("tom")
.password(passwordEncoder().encode("123"))
.roles("MANAGER")
.and()
.withUser("jerry")
.password(passwordEncoder().encode("123"))
.roles("STUDENT");
}
/**
* 从SpringSecurity5.0开始,强制要求密码加密,在测试类中有测试代码
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
@Test
void testPassword(){
String source = "123";
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String p1 = passwordEncoder.encode(source);
System.out.println(p1);
String p2 = passwordEncoder.encode(source);
System.out.println(p2);
String p3 = passwordEncoder.encode(source);
System.out.println(p3);
//密码加密对比
System.out.println(passwordEncoder.matches(source, p1));
System.out.println(passwordEncoder.matches(source, p2));
System.out.println(passwordEncoder.matches(source, p3));
}
@RestController
public class CommonController {
@GetMapping("/admin/query")
public String adminQuery(){
return "admin-query";
}
@GetMapping("/manager/save")
public String managerSave(){
return "manager-save";
}
@GetMapping("/student/remove")
public String studentRemove(){
return "student-remove";
}
}
/**
* 配置路径与权限的关系
* 没有配置的路径,可以正常访问
* 权限不足响应状态码是 403
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//此方法中http参数必须调用formLogin()否则原生登录和登录页面都会消失
http.formLogin();
http.authorizeHttpRequests()
//编程式权限鉴定(推荐使用声明式 既注解)
.antMatchers("/admin/query").hasRole("ADMIN")
.antMatchers("/manager/**").hasRole("MANAGER")
.antMatchers("/student/remove").hasRole("STUDENT")
//所有的请求都必须进行身份认证
.anyRequest().authenticated();
}
权限不足页面:resources/static/error/403.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>权限鉴定失败title>
head>
<body>
<div style="text-align: center">
<h1>403你没有权限访问该资源!h1>
div>
body>
html>
/**
* 获取用户登录信息的两种方法
* 获取到的密码是空的
*/
@RestController
public class GetUserInfoController {
/**
* 方法一:只能用在Controller层,Principal参数可以直接用
* 当前登录用户信息Principal
*/
@GetMapping("/userInfo")
public Principal userInfo(Principal principal){
return principal;
}
/**
* 方法二:也可用于Service层
* SecurityContext 安全框架的上下文对象
*/
@GetMapping("/getUserInfo")
public Object getUserInfo(){
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
return authentication;
}
}
WebSecurityConfig.java 配置类
/**
* SpringSecurity的配置类
* 这里配置了,配置文件里的就失效了
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置Security登录的用户信息
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中配置用户
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123"))
//配置 角色
//如果为用户配置的是角色,那么系统会在角色字符串前追加一个ROLE_的前缀,表示当前是角色
.roles("ADMIN")
//配置 权限
//参数为:权限字符串 格式为-->模块名称:操作名称
.authorities("sys:query", "sys:save", "sys:update", "sys:delete")
.and()
.withUser("tom")
.password(passwordEncoder().encode("123"))
.roles("MANAGER")
.authorities("sys:query", "sys:save")
.and()
.withUser("jerry")
.password(passwordEncoder().encode("123"))
.roles("STUDENT");
}
/**
* 配置路径与权限的关系
* 没有配置的路径,可以正常访问
* 权限不足响应状态码是 403
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//此方法中http参数必须调用formLogin()否则原生登录和登录页面都会消失
http.formLogin();
http.authorizeHttpRequests()
//编程式权限鉴定(推荐使用声明式 既注解)
.antMatchers("/admin/query").hasRole("ADMIN")
.antMatchers("/manager/**").hasRole("MANAGER")
.antMatchers("/student/remove").hasRole("STUDENT")
.antMatchers("/sys/query").hasAuthority("sys:query")
.antMatchers("/sys/save").hasAuthority("sys:save")
.antMatchers("/sys/update").hasAuthority("sys:update")
.antMatchers("/sys/delete").hasAuthority("sys:delete")
//所有的请求都必须进行身份认证
.anyRequest().authenticated();
}
/**
* 从SpringSecurity5.0开始,强制要求密码加密,在测试类中有测试代码
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
Controller类
@RestController
@RequestMapping("/sys")
public class SysController {
@GetMapping("/query")
public String query(){
return "sys-query";
}
@GetMapping("/save")
public String save(){
return "sys-save";
}
@GetMapping("/update")
public String update(){
return "sys-update";
}
@GetMapping("/delete")
public String delete(){
return "sys-delete";
}
}
在配置类上开启注解支持
@Configuration
//开启注解式鉴权,prePostEnabled开启方法的前置与后置鉴权
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
}
在方法上开启鉴权
@RestController
@RequestMapping("/sys")
public class SysController {
@GetMapping("/query")
@PreAuthorize("hasAuthority('sys:query')")
public String query(){
return "sys-query";
}
@GetMapping("/save")
@PreAuthorize("hasAuthority('sys:save')")
public String save(){
return "sys-save";
}
@GetMapping("/update")
@PreAuthorize("hasAuthority('sys:update')")
public String update(){
return "sys-update";
}
@GetMapping("/delete")
@PreAuthorize("hasAuthority('sys:delete')")
public String delete(){
return "sys-delete";
}
}
权限鉴定处理器实现类:AccessDeniedHandlerImpl.java
/**
* 权限不足(鉴权失败)处理器
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
/**
* 鉴权失败处理方法
* @param request 原生请求对象
* @param response 原生响应对象
* @param accessDeniedException 鉴权失败异常对象
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
//设置内容类型
// response.setContentType("application/json;charset=utf-8");
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); //可以使用spring提供的常量
//将json数据写出到客户端
//ObjectMapper是将Java对象转换为json字符串,将json字符串转换为Java对象的工具类
ObjectMapper objectMapper = new ObjectMapper();
response.getWriter().write(objectMapper.writeValueAsString(Result.error(-20, "您没有权限访问")));
}
}
配置类 自动装配
//自动装配处理器
//鉴权失败处理器
@Autowired
private AccessDeniedHandler accessDeniedHandler;
配置鉴权失败处理器
@Override
protected void configure(HttpSecurity http) throws Exception {
...
//配置鉴权失败处理器
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
...
}
主要用于异步请求,前后端分离
返回的是 json 字符串
登录成功处理器实现类
/**
* 身份认证成功处理器
*/
@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
/**
* 身份认证成功之后的处理
* @param request 请求对象
* @param response 响应对象
* @param authentication 当前登录用户对象
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//设置内容类型
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//将json数据写出到客户端
response.getWriter().write(new ObjectMapper().writeValueAsString(Result.success("登录成功")));
}
}
登录失败处理器实现类
/**
* 身份认证失败处理器
*/
@Component
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {
/**
* 身份认证失败之后处理
* @param request 请求对象
* @param response 响应对象
* @param exception 身份认证失败异常对象
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//设置内容类型
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
String message = "";
if(exception instanceof UsernameNotFoundException){
message = "用户名有误";
}else if(exception instanceof BadCredentialsException){
message = "用户名或密码有误";
}else if(exception instanceof LockedException){
message = "当前用户被锁定,请联系管理员";
}else if(exception instanceof DisabledException){
message = "当前用户被禁用,请联系管理员";
}else if(exception instanceof AccountExpiredException){
message = "账号已过期,请联系管理员";
}else if(exception instanceof CredentialsExpiredException){
message = "密码已过期,请联系管理员";
}
response.getWriter().write(new ObjectMapper().writeValueAsString(Result.error(-10, message)));
}
}
自动注入到配置类
//身份认证成功与失败处理器
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
配置处理器
@Override
protected void configure(HttpSecurity http) throws Exception {
//此方法中http参数必须调用formLogin()否则原生登录和登录页面都会消失
http.formLogin()
//同步请求处理
// .successForwardUrl()
// .failureUrl();
//异步请求处理
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler);
...
}
Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。
根据前边知识的学习,可以通过Filter或AOP等技术来实现,SpringSecurity对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。
当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain 的Servlet过滤器,类型为org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YHlHnBb8-1661775322058)(E:\JAVA\DLJD\我的笔记\myImages\SpringSecurity知识点总结\clip_image002.png)]
FilterChainProxy 是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理
前面我们在配置类中设置用户名和密码,其实存储在了内存中
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中配置用户
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123"))
.roles("ADMIN");
}
而实际开发中我们想要使用数据库就要实现UserDetailsService接口,
角色和权限可以同时配置
service–> UserDetailsServiceImpl
/**
* 实现UserDetailsService方法,可以查自己的数据库,不用默认的了
* 在SpringSecurity中整个身份认证操作都由它自己独立完成
* 程序员仅需要提供用户的查询方式即可
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("用户名:" + username);
//模拟从数据库查询用户: 用户信息暂时使用静态数据替代
return User.withUsername(username)
.password(new BCryptPasswordEncoder().encode("123"))
.authorities("sys:query", "sys:delete", "ROLE_ADMIN")
.build();
}
}
WebSecurityConfig 配置类
/**
* 注入自定义用户查询类UserDetailsService的实现类
*/
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置自定义的UserDetailsService实现类来获取用户信息
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
Controller
@RestController
public class CommonController {
@GetMapping("/admin/query")
@PreAuthorize("hasRole('ADMIN')")
public String adminQuery(){
return "admin-query";
}
@GetMapping("/manager/save")
@PreAuthorize("hasRole('MANAGER')")
public String managerSave(){
return "manager-save";
}
@GetMapping("/student/remove")
@PreAuthorize("hasRole('STUDENT')")
public String studentRemove(){
return "student-remove";
}
}
实现了UserDetails接口,后面的service层需要使用
包括用户的属性、用户的所有权限、用户的所有角色
public class LoginUser implements UserDetails {
private Integer id;
private String username;
private String mobile;
private String nickname;
private String email;
private String gender;
private String avatar;
private String userpwd;
private String userType;
private String status;
/**
* 用户拥有的角色名称列表
*/
private List<String> roleNameList;
/**
* 用户拥有的权限字符串列表
*/
private List<String> percodeList;
/**
* 当前登录用户拥有的权限
*/
@Override
//json序列化忽略属性,转json是将会被忽略
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
HashSet<GrantedAuthority> grantedAuthorityHashSet = new HashSet<>();
//添加角色
if(!CollectionUtils.isEmpty(roleNameList)){
for (String roleName : roleNameList) {
if(StringUtils.hasLength(roleName)){
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_" + roleName);
grantedAuthorityHashSet.add(simpleGrantedAuthority);
}
}
}
//添加权限
if(!CollectionUtils.isEmpty(percodeList)){
for (String percode : percodeList) {
if(StringUtils.hasLength(percode)){
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(percode);
grantedAuthorityHashSet.add(simpleGrantedAuthority);
}
}
}
return grantedAuthorityHashSet;
}
@Override
@JsonIgnore
public String getPassword() {
return this.userpwd;
}
@Override
public String getUsername() {
return this.username;
}
@Override
@JsonIgnore
public boolean isAccountNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isAccountNonLocked() {
return true;
}
@Override
@JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isEnabled() {
return "1".equals(this.status);
}
//get... set...
}
返回的是自定义的用户对象
根据用户名获取用户的信息、角色和权限,统统获取
Service层 —> UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysPermissionService sysPermissionService;
/**
* 根据用户名查询用户
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = sysUserService.getOne(new QueryWrapper<SysUser>().eq("username", username));
if(sysUser == null){
throw new UsernameNotFoundException("用户不存在");
}
//根据用户ID查询当前用户对应的角色和权限
List<SysRole> roleList = sysRoleService.listByUserId(sysUser.getId());
List<SysPermission> permissionList = sysPermissionService.listByUserId(sysUser.getId());
//创建用户登录对象
LoginUser loginUser = new LoginUser();
//复制对象属性值
BeanUtils.copyProperties(sysUser, loginUser);
//设置角色名称和权限字符串列表
loginUser.setRoleNameList(roleList.stream().map(SysRole::getRolename).collect(Collectors.toList()));
loginUser.setPercodeList(permissionList.stream().map(SysPermission::getPercode).collect(Collectors.toList()));
//loginUser实现了UserDetails接口,可以直接返回
return loginUser;
}
}
前面的鉴权处理器、登录成功、失败处理器,都可以拿来直接用
未登录处理器
/**
* 未认证处理器(未登录)
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//设置内容类型
// response.setContentType("application/json;charset=utf-8");
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//将json数据写出到客户端
// response.getWriter().write(new ObjectMapper().writeValueAsString(Result.error(-10, "未登陆,请先登陆")));
response.getWriter().write(JsonUtil.toString(Result.error(-10, "未登陆,请先登陆")));
}
}
登出成功处理器
/**
* 登出成功处理器
*/
@Component
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//设置内容类型
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//将json数据写出到客户端
// response.getWriter().write(new ObjectMapper().writeValueAsString(Result.success()));
response.getWriter().write(JsonUtil.toString(Result.success()));
}
}
配置用户信息、权限、处理器
/**
* SpringSecurity配置类
* 开启注解式鉴权
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 依赖注入 5个处理器 userDetailsService
*/
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private LogoutSuccessHandler logoutSuccessHandler;
@Autowired
private UserDetailsService userDetailsService;
/**
* 配置用户信息
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
/**
* 配置http安全校验等
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//开启跨域
.cors()
.and()
//关闭跨站请求伪造防护,如果不是前后端分离可以不关闭
.csrf().disable()
//请求权限的配置
.authorizeHttpRequests()
//配置url白名单(无需进行身份认证的url)
//permitAll()拥有全部权限 anonymous()匿名访问(不登录时可访问,登陆后不可访问)
.antMatchers("/auth/login", "/webjars/**", "/swagger-resources/**", "/doc.html", "/v2/api-docs").permitAll()
//所有请求都需要进行身份认证
.anyRequest().authenticated()
.and()
//配置无权限处理器,未认证处理器
.exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint)
.and()
//配置登录相关信息
// .formLogin().loginProcessingUrl("/auth/login").usernameParameter("username").passwordParameter("userpwd")
// .successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler)
// .and()
//配置登出
.logout().logoutUrl("/auth/logout").logoutSuccessHandler(logoutSuccessHandler)
.and()
//添加自定义过滤器
.addFilter(loginAuthenticationFilter())
.addFilter(new JwtTokenVerifyFilter(authenticationManagerBean()))
//前后台分离的项目中,不再使用HttpSession维持会话,直接禁用
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
/**
* 配置自定义的登录过滤器
*/
@Bean
public LoginAuthenticationFilter loginAuthenticationFilter() throws Exception {
LoginAuthenticationFilter loginAuthenticationFilter = new LoginAuthenticationFilter();
loginAuthenticationFilter.setFilterProcessesUrl("/auth/login");
loginAuthenticationFilter.setUsernameParameter("username");
loginAuthenticationFilter.setPasswordParameter("userpwd");
loginAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
loginAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
//设置身份认证管理对象
loginAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
return loginAuthenticationFilter;
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
@GetMapping("/list")
@PreAuthorize("hasAuthority('sys:user:query')")
public Result list(){
return Result.success(sysUserService.list());
}
默认的过滤器接收的是名值对,无法接受json格式,为了解决这个问题,所以自定义一个过滤器
/**
* 自定义登录过滤器
* 要求:前端提交post请求,并且数据为json字符串
*/
public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//判断请求是否为post请求
if(!"POST".equalsIgnoreCase(request.getMethod())){
throw new AuthenticationServiceException("登录方法不支持" + request.getMethod() + "请求方式");
}
//判断数据是否为json
if(!request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)){
throw new AuthenticationServiceException("登录数据必须为json格式");
}
//接收json数据
SysUser sysUser = null;
try {
sysUser = JsonUtil.toBean(request.getInputStream(), SysUser.class);
} catch (IOException e) {
e.printStackTrace();
}
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getUserpwd());
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
用到的 Json 工具类
/**
* jackson工具类
*/
public class JsonUtil {
public static final ObjectMapper mapper = new ObjectMapper();
/**
* 将对象转换为json字符串
*/
public static String toString(Object obj){
if(obj == null){
return null;
}
if(obj.getClass() == String.class){
return (String) obj;
}
try {
return mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
/**
* 将字符串转换为对象
*/
public static <T> T toBean(String json, Class<T> tClass){
try {
return mapper.readValue(json, tClass);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
/**
* 将字节流(传输json串)转换为对象
*/
public static <T> T toBean(InputStream in, Class<T> tClass){
try {
return mapper.readValue(in, tClass);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
在配置类中配置登录过滤器,前面写过了,这里就不重复了
工具类
public class SecurityUtil {
/**
* 获取当前登录用于的Authentication对象
*/
public static Authentication getAuthentication(){
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* 获取自定义的登录对象LoginUser
*/
public static LoginUser getLoginUser(){
return (LoginUser) getAuthentication().getPrincipal();
}
/**
* 获取用户账号
*/
public static String getUsername(){
return getLoginUser().getUsername();
}
/**
* 获取用户ID
*/
public static Integer getUserId(){
return getLoginUser().getId();
}
}
使用 Controller中能用到
/**
* 新增数据
*/
@PostMapping("/save")
@PreAuthorize("hasAuthority('sys:user:save')")
public Result save(@RequestBody SysUser entity){
entity.setCreateUser(SecurityUtil.getUsername());
entity.setUpdateUser(SecurityUtil.getUsername());
entity.setCreateTime(new Date());
entity.setUpdateTime(new Date());
return Result.success(sysUserService.add(entity));
}
/**
* 编辑数据
*/
@PutMapping("/edit")
@PreAuthorize("hasAuthority('sys:user:update')")
public Result edit(@RequestBody SysUser entity){
entity.setUpdateUser(SecurityUtil.getUsername());
entity.setUpdateTime(new Date());
return Result.success(sysUserService.edit(entity));
}
使用思路
依赖
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwt-apiartifactId>
<version>0.11.2version>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwt-implartifactId>
<version>0.11.2version>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwt-jacksonartifactId>
<version>0.11.2version>
dependency>
JwtUtil
/**
* jwt的工具类
*/
public class JwtUtil {
/**
* 密钥字符串
*/
private static final String SECRET_KEY = "powernode2022powernode2022powernode2022";
/**
* 生成jwt字符串
*/
public static String generateToken(LoginUser loginUser, long ttl){
long currentTimeMillis = System.currentTimeMillis();
return Jwts.builder()
//在荷载部分中添加自定义的数据
.claim("loginUser", JsonUtil.toString(loginUser))
// .addClaims()
//唯一标识
.setId(UUID.randomUUID().toString())
//签发时间:字符串生成时间
.setIssuedAt(new Date(currentTimeMillis))
//签发人
.setSubject("POWERNODE")
//过期时间
.setExpiration(new Date(currentTimeMillis+ttl))
//生成jwt时使用的算法和密钥
.signWith(generalKey(), SignatureAlgorithm.HS256)
.compact();
}
/**
* 加密密钥
*/
public static SecretKey generalKey(){
return new SecretKeySpec(SECRET_KEY.getBytes(), SignatureAlgorithm.HS256.getJcaName());
}
/**
* 解析jwt
*/
public static Claims parseToken(String jwtToken){
Claims claims = Jwts.parserBuilder()
//设置签名的密钥
.setSigningKey(generalKey())
.build()
//设置需要解析的jwt字符串
.parseClaimsJws(jwtToken)
.getBody();
return claims;
}
/**
* 获取用户登录对象
*/
public static LoginUser getLoginUser(String jwtToken){
Claims claims = parseToken(jwtToken);
String loginUserJosn = claims.get("loginUser").toString();
return JsonUtil.toBean(loginUserJosn, LoginUser.class);
}
public static void main(String[] args) {
LoginUser loginUser = new LoginUser();
loginUser.setUsername("jerry");
loginUser.setPercodeList(Arrays.asList("sys:query", "sys:save", "sys:update"));
String token = generateToken(loginUser, 1000*60*60*24);
System.out.println(token);
}
}
处理器
/**
* 身份认证成功处理器
*/
@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
/**
* 身份认证成功之后的处理
* @param request 请求对象
* @param response 响应对象
* @param authentication 当前登录用户对象
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//设置内容类型
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//获取当前用户登录
LoginUser loginUser = SecurityUtil.getLoginUser();
String jwtToken = JwtUtil.generateToken(loginUser, 1000*60*60*24);
//将jwt字符串设置到响应头中
response.setHeader("Authorization", "Bearer " + jwtToken);
//将json数据写出到客户端
// response.getWriter().write(new ObjectMapper().writeValueAsString(Result.success()));
response.getWriter().write(JsonUtil.toString(Result.success(loginUser)));
}
}
跨域配置中设置响应头
//设置暴露给前端响应头
corsConfiguration.addExposedHeader("Authorization");
过滤器
public class JwtTokenVerifyFilter extends BasicAuthenticationFilter {
private final AuthenticationManager authenticationManager;
/**
* 有参数的构造方法
*/
public JwtTokenVerifyFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
this.authenticationManager = authenticationManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//从请求头中获取jwtToken字符串
String token = request.getHeader("Authorization");
if(!StringUtils.hasLength(token) || !token.startsWith("Bearer ")){
chain.doFilter(request, response);
//设置未登录提示
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(JsonUtil.toString(Result.error(-10, "未登陆,请先登陆")));
}else{
//从jwt字符串中获取LoginUser对象
LoginUser loginUser = JwtUtil.getLoginUser(token.replace("Bearer ", ""));
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
//将UsernamePasswordAuthenticationToken放入SecurityContext上下文
SecurityContextHolder.getContext().setAuthentication(authRequest);
chain.doFilter(request, response);
}
}
}
配置过滤器,禁用session
//添加自定义过滤器
.addFilter(loginAuthenticationFilter())
.addFilter(new JwtTokenVerifyFilter(authenticationManagerBean()))
//前后台分离的项目中,不再使用HttpSession维持会话,直接禁用
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
esponse, FilterChain chain) throws IOException, ServletException {
//从请求头中获取jwtToken字符串
String token = request.getHeader(“Authorization”);
if(!StringUtils.hasLength(token) || !token.startsWith("Bearer ")){
chain.doFilter(request, response);
//设置未登录提示
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(JsonUtil.toString(Result.error(-10, “未登陆,请先登陆”)));
}else{
//从jwt字符串中获取LoginUser对象
LoginUser loginUser = JwtUtil.getLoginUser(token.replace("Bearer ", “”));
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
//将UsernamePasswordAuthenticationToken放入SecurityContext上下文
SecurityContextHolder.getContext().setAuthentication(authRequest);
chain.doFilter(request, response);
}
}
}
- 配置过滤器,禁用session
```java
//添加自定义过滤器
.addFilter(loginAuthenticationFilter())
.addFilter(new JwtTokenVerifyFilter(authenticationManagerBean()))
//前后台分离的项目中,不再使用HttpSession维持会话,直接禁用
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);