一、Spring Security对接到RBAC
对接原理
通过UserDetailsService和UserDetails这两个接口为Spring Security提供认证数据。
用户登录之后先认证,通过认证管理器访问访问service层
首先创建VO类实现用户登录中信息的传递:
/**
* @document: 管理员登录信息的VO类 向页面返回登录的管理员信息
* @Author:SmallG
* @CreateTime:2023/9/27+9:08
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminLoginInfoVO implements Serializable {
/* 数据ID */
private Long id;
/* 用户名 */
private String username;
/* 密码(密文)*/
private String password;
/* 是否启用,1=启用,0=禁用 */
private Integer enable;
/* 权限列表 */
private List<String> permissions;
/* 角色列表 */
private List<String> roles;
}
adminMapper接口中写用户登录的方法:
/**
* 根据用户名查询管理员登录信息
* @param username 用户名
* @return 匹配登录信息,如果没有匹配的数据返回null
*/
AdminLoginInfoVO getLoginInfoByUsername(String username);
编写sql语句:
<!-- 对象关系映射 -->
<resultMap id="LoginInfoResultMap" type="cn.highedu.csmall.pojo.vo.AdminLoginInfoVO">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="enable" property="enable"></result>
<!--一对多-->
<collection property="roles" ofType="String">
<!--相当于调用String类的构造方法 创建String对象-->
<constructor>
<!-- 创建String对象传递的参数 角色名-->
<arg column="name"/>
</constructor>
</collection>
<collection property="permissions" ofType="String">
<constructor>
<!-- 创建String对象传递的参数 权限值-->
<arg column="value"/>
</constructor>
</collection>
</resultMap>
<select id="getLoginInfoByUsername" resultMap="LoginInfoResultMap">
SELECT ams_admin.id,
ams_admin.username,
ams_admin.password,
ams_admin.enable,
ams_role.name,
ams_permission.value
FROM ams_admin
left join ams_admin_role on ams_admin.id = ams_admin_role.admin_id
left join ams_role on ams_admin_role.role_id = ams_role.id
left join ams_role_permission on ams_role.id = ams_role_permission.role_id
left join ams_permission on ams_role_permission.permission_id = ams_permissio.id
WHERE ams_admin.username = #{username}
</select>
实现UserDetails接口:
/**
* @document: 封装登录管理员的信息
* @Author:SmallG
* @CreateTime:2023/9/27+10:55
*/
@Slf4j
public class UserDetailsImpl implements UserDetails {
//定义一个属性
private AdminLoginInfoVO admin;
//封装用户信息
private List<GrantedAuthority> authorities = new ArrayList<>();
//定义构造方法 用于封装角色和权限
public UserDetailsImpl(AdminLoginInfoVO admin) {
this.admin = admin;
//读取角色和权限
List<String> roles = admin.getRoles();
List<String> permissions = admin.getPermissions();
//把角色和权限封装到authorities中
if (roles != null) {
for (String role : roles) {
//角色以ROLE_作为前缀
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
}
if (permissions != null) {
for (String permission : permissions) {
//权限没有后缀,可以不用加
authorities.add(new SimpleGrantedAuthority(permission));
}
}
}
//返回角色和权限
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return admin.getPassword();
}
@Override
public String getUsername() {
return admin.getUsername();
}
// 返回false表示用户过期
@Override
public boolean isAccountNonExpired() {
return true;
}
//false表示锁定(例如三次输错密码锁定账户) true表示用户不被锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
//false表示过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//false表示禁用
@Override
public boolean isEnabled() {
return true;
}
}
实现UserDetailsService接口:
/**
* @document: 查询管理员信息
* @Author:SmallG
* @CreateTime:2023/9/27+10:55
*/
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private AdminMapper adminMapper;
//用于查询用户登录信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("Spring Security查询用户信息,参数:{}", username);
AdminLoginInfoVO adminLoginInfoVO = adminMapper.getLoginInfoByUsername(username);
if (adminLoginInfoVO != null) {
return new UserDetailsImpl(adminLoginInfoVO);
}
throw new UsernameNotFoundException("用户名不存在");
}
}
重启服务
二、跨域验证与JWT
1、跨域
前后端分离项目中,端口号不同,需要跨域认证才能彼此访问。
2、JWT
JSON Web Token,是一种用于进行身份认证和授权的开放标准,基于JSON格式的数据结构,在网络应用中被广泛使用。
JWT通常由三个部分组成,分别为头部(Header)、载荷(Payload)和签名(Signature)。
JWT不能存敏感信息,比如手机号、身份证号、密码等
流程简述:
- 用户向服务器发送登录请求,服务器进行身份验证。
- 验证成功后,服务器生成一个JWT,并将其发送给客户端。
- 客户端将JWT保存在本地,并在后续的请求中将其发送给服务器。
- 服务器验证JWT的有效性,如果验证通过,则执行请求操作。
3、使用JWT
1、Header(头)
Header通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法。它是一个JSON对象
2、Payload(栽荷)
Payload也是一个JSON对象,包含了一些声明和数据。这些声明称为Claim,用于描述JWT令牌中包含的信息,例如用户ID、用户名、过期时间等。Claim分为三类:注册的声明、公共的声明和私有的声明。
3、Signature
Signature用于验证JWT的真实性,防止JWT被篡改。Signature的计算需要使用Header和Payload中的数据以及一个密钥(secret)。计算步骤如下:
- 使用Base64Url编码后的Header和Base64Url编码后的Payload使用句号(.)连接起来,得到一个字符串。
- 使用Header中指定的签名算法对该字符串进行签名,得到一个签名值。
- 使用Base64Url编码将签名值转换为字符串,得到Signature。
4、JJWT API
(1)引入依赖
<!-- 添加JJWT依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- JJWT 需要依赖 JAXB -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
(2)Token测试
@Slf4j
public class TokenTest {
//定义签名钥匙
private String SECRET_KEY = "123456789012345678901234567890";
/**
* 用于生成token
*
* @return
*/
private String generateToken() {
//定义当前日期
Date now = new Date();
//定义过期时间 7天
Date expireDate = new Date(now.getTime() + 1000 * 60 * 60 * 24 * 7);
//定义一个token
String token = Jwts.builder()
.setIssuedAt(now) //设置签发时间
.setExpiration(expireDate) //过期时间
.setSubject("admin") //面向用户
.claim("id", "666666") //设置自定义内容 自定义要求
.claim("roles", "admin,manager,user")
.claim("permissions", "user:list,user:add,user:delete,user:update")
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) //设置签名算法 (加密算法,自定义钥匙)
.compact(); //生成token
log.info("token:{}", token);
return token;
}
//解析Token
private void parseToken(String token) {
//解析Token
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY) //设置钥匙
.parseClaimsJws(token) //设置及诶西的token
.getBody();
//获取token中的信息
Date issuedAt = claims.getIssuedAt();
//获取过期时间
Date expiration = claims.getExpiration();
String subject = claims.getSubject();//面向的用户
String id = claims.get("id",String.class);//获取自定义信息
String roles = claims.get("roles",String.class);
String permissions = claims.get("permissions",String.class);
log.info("签发时间:{}", issuedAt);
log.info("过期时间:{}", expiration);
log.info("面向的用户:{}", subject);
log.info("id:{}", id);
log.info("角色:{}", roles);
log.info("权限:{}", permissions);
}
@Test
public void test() {
String token = generateToken();
parseToken(token);
}
}
(3)在application-dev里配置token的钥匙和有效期:
# 配置token的钥匙和有效期
jwt:
secret-key: coolshark_key_1234567890
duration-in-minute: 60
(4)创建Service
Service用于生成和解析Token,在解析token出错时,应该也有个状态码,所以在ServiceCode中添加解析错误的状态码:
/**
* 错误:JWT过期
*/
ERR_JWT_EXPIRED(60000),
/**
* 错误:验证签名失败
*/
ERR_JWT_SIGNATURE(60100),
/**
* 错误:JWT格式错误
*/
ERR_JWT_MALFORMED(60200),
/**
* 错误:JWT已退出
*/
ERR_JWT_LOGOUT(60300),
/**
* 未知错误
*/
ERR_UNKNOWN(99999);
创建service:
/**
* @document: 生成和解析Token
* 例如把登录用户AdminLoginInfoVO保存在Token中
* 把Token解析为AdminLoginInfoVO对象
* @Author:SmallG
* @CreateTime:2023/9/27+14:06
*/
@Service
@Slf4j
public class JwtTokenService {
@Value("${jwt.secret-key}")
private String secretKey;
@Value("${jwt.duration-in-minute}")
private Long durationInMinute;
/**
* 把UserDetailsImpl对象转换为JWT字符串
*
* @param userDetails
* @return
*/
public String createToken(UserDetailsImpl userDetails) {
AdminLoginInfoVO adminLoginInfoVO = userDetails.getAdmin();
return createToken(adminLoginInfoVO);
}
/**
* 把JWT字符串解析为UserDetailsImpl对象
* @param jwt
* @return
*/
public UserDetailsImpl parseJwtToken(String jwt){
AdminLoginInfoVO adminLoginInfoVO = parseToken(jwt);
return new UserDetailsImpl(adminLoginInfoVO);
}
/**
* 把adminLoginInfoVO对象转换为JWT字符串
*
* @param adminLoginInfoVO
* @return
*/
public String createToken(AdminLoginInfoVO adminLoginInfoVO) {
log.info("开始创建JWT,参数:{}", adminLoginInfoVO);
String subject = adminLoginInfoVO.getUsername();
Date now = new Date();
Date exp = new Date(now.getTime() + durationInMinute * 60 * 1000);//转换成毫秒
Long id = adminLoginInfoVO.getId();
Integer enable = adminLoginInfoVO.getEnable();
//把集合中的数据取出来,中间用逗号隔开
String roles = String.join(",", adminLoginInfoVO.getRoles());
String permissions = String.join(",", adminLoginInfoVO.getPermissions());
//创建JWT字符串
String jwt = Jwts.builder().setSubject(subject)
.setIssuedAt(now)
.setExpiration(exp)
.claim("id", id)
.claim("enable", enable)
.claim("roles", roles)
.claim("permissions", permissions)
.signWith(SignatureAlgorithm.HS256, secretKey) //算法加钥匙
.compact();
log.info("创建JWT成功,结果:{}", jwt);
return jwt;
}
/**
* 把JWT字符串解析为AdminLoginInfoVO对象
*
* @param jwt
* @return
*/
public AdminLoginInfoVO parseToken(String jwt) {
log.info("开始解析JWT,参数:{}", jwt);
try {
Claims claims = Jwts.parser().setSigningKey(secretKey)
.parseClaimsJws(jwt).getBody();
//从JWT中获取管理员信息
Long id = Long.valueOf(claims.get("id").toString());
String username = claims.getSubject();
Integer enable = Integer.valueOf(claims.get("enable").toString());
String roles = claims.get("roles").toString();
String permissions = claims.get("permissions").toString();
//创建adminLoginInfoVO对象
AdminLoginInfoVO adminLoginInfoVO = new AdminLoginInfoVO();
adminLoginInfoVO.setId(id);
adminLoginInfoVO.setUsername(username);
adminLoginInfoVO.setEnable(enable);
adminLoginInfoVO.setRoles(Arrays.asList(roles.split(",")));
adminLoginInfoVO.setPermissions(Arrays.asList(permissions.split(",")));
log.info("解析JWT成功,结果:{}", adminLoginInfoVO);
return adminLoginInfoVO;
} catch (ExpiredJwtException e) {
log.error("JWT已过期", e);
throw new ServiceException(ServiceCode.ERR_JWT_EXPIRED, "JWT已过期");
} catch (SignatureException e) {
log.error("解析JWT失败");
throw new ServiceException(ServiceCode.ERR_JWT_SIGNATURE, "令牌无效");
} catch (Exception e) {
log.error("解析JWT失败");
throw new ServiceException(ServiceCode.ERR_JWT_MALFORMED,"格式错误");
}
}
}
编写测试类对以上代码进行测试:
@Slf4j
@SpringBootTest
class JwtTokenServiceTest {
@Autowired
private JwtTokenService jwtTokenService;
@Autowired
private AdminMapper adminMapper;
@Test
public void test(){
//登录用户
AdminLoginInfoVO adminLoginInfoVO = adminMapper.getLoginInfoByUsername("root");
String jwt = jwtTokenService.createToken(adminLoginInfoVO);
log.info("Token:{}",jwt);
AdminLoginInfoVO admin = jwtTokenService.parseToken(jwt);
log.info("解析的对象:{}",admin);
}
}
测试结果:
(5)Spring Security 自定义登录
在WebSecurityConfig中配置类中定义一个AuthenticationManager的Bean:
@Configuration
@EnableWebSecurity //开启登录认证授权
public class WebSecurityConfig {
//认证管理器
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager(); //返回认证管理器
}
}
创建JwtVO
/**
* @document: 包含Token的VO对象
* @Author:SmallG
* @CreateTime:2023/9/27+15:20
*/
@Data
@AllArgsConstructor
public class JwtVO {
private String token;
}
创建AdminLoginInfoDTO,用于返回页面的登录信息:
@Data
public class AdminLoginInfoDTO implements Serializable {
private String username;
private String password; //明文
}
在IadminService中添加登录成功返回JWT令牌的方法
/**
* 前端通过表单登录 返回token信息
* @param adminLoginInfoDTO 封装了用户登录信息对象
* @return 返回JWT令牌(登录成功)
*/
JwtVO login(AdminLoginInfoDTO adminLoginInfoDTO);
实现方法:
@Override
public JwtVO login(AdminLoginInfoDTO adminLoginInfoDTO) {
//1、创建认证对象,传入用户名和密码
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(adminLoginInfoDTO.getUsername(), adminLoginInfoDTO.getPassword());
//2、调用认证管理器的方法,传入认证对象(主要传入用户名和密码)
Authentication authenticate = authenticationManager.authenticate(token);
//3、如果认证成功会将认证信息存入上下文对象(SecurityContext)服务只要不关闭,这个上下文对象数据一直存在
//如果没有存储信息 说明认证失败
SecurityContextHolder.getContext().setAuthentication(authenticate);
//获取认证的主体
Object principal = authenticate.getPrincipal();
//认证的主体是UserDetails的实现类 UserDetailsImpl类的对象
UserDetailsImpl userDetails = (UserDetailsImpl) principal;
//4、认证成功生成token
String tokenString = jwtTokenService.createToken(userDetails);
//5、把token返回给客户端
return new JwtVO(tokenString);
}
在AdminController里面写一个登录方法:
@PostMapping("/login")
public JsonResult<JwtVO> login(@RequestBody AdminLoginInfoDTO adminLoginInfoDTO) {
log.info("开始处理【管理员登录】的请求,参数:{}", adminLoginInfoDTO);
JwtVO token = adminService.login(adminLoginInfoDTO);
return JsonResult.ok(token);
}
在AuthController写登录时获取用户名和头像的信息:
/**
* 获取当前登录管理员的信息(用户名、头像)
* @AuthenticationPrincipal 从上下文对象获取UserDetailsImpl对象
* @return
*/
@GetMapping("/info")
public JsonResult<AdminLoginInfoVO> getInfo(@AuthenticationPrincipal UserDetailsImpl userDetails) {
//根据token查询管理员信息
log.info("开始处理【根据token查询管理员信息】的请求,参数:{}",userDetails);
AdminLoginInfoVO adminLoginInfoVO = userDetails.getAdmin();
log.info("管理员信息:{}",adminLoginInfoVO);
return JsonResult.ok(adminLoginInfoVO);
}
在WebSecurityConfig中设置授权认证规则:
@Configuration
@EnableWebSecurity //开启登录认证授权
public class WebSecurityConfig {
//认证管理器
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager(); //返回认证管理器
}
/**
* 设置URL授权规则
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//不需要认证的url
String[] permitAllUrls = {
"/auth/**",
"/error/**"
};
//配置不需要认证的请求路径
http.authorizeHttpRequests((authorization ->
authorization.requestMatchers(permitAllUrls).permitAll()));
//配置需要认证的请求路径
http.authorizeHttpRequests(authorization->
authorization.anyRequest().authenticated());
//禁用跨域防护机制(不推荐)
http.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
5、利用过滤器检验JWT
主要用于检查请求是否包含有效的JWT令牌,以验证用户身份。
JWT认证过滤器的主要功能如下:
- 检查请求是否包含有效的JWT令牌:JWT认证过滤器会检查每个请求是否包含有效的JWT令牌。如果请求中没有包含JWT令牌或者JWT令牌无效,则会返回一个未经授权的错误响应。
- 解码JWT令牌并验证其有效性:JWT认证过滤器会解码请求中的JWT令牌,并使用密钥对其进行验证。如果JWT令牌无效,则会返回一个未经授权的错误响应。
- 将解码后的用户信息存储到Spring Security上下文中:如果JWT令牌有效,则JWT认证过滤器会将从JWT令牌中解码的用户信息存储到Spring Security的上下文中,以便后续的访问授权使用。
(1)验证Token过滤器
创建filter包中新建JwtAuthenticationFilter类:
/**
* @document: JWT认证过滤器
* 在发送请求的过程中 一般把token放在请求头中
* @Author:SmallG
* @CreateTime:2023/9/27+16:36
*/
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenService jwtTokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取请求头
String jwtToken = request.getHeader("Authorization");
log.info("jwtToken:{}", jwtToken);
//不需要认证的请求,直接访问 例如登录、注册、验证码等
if (jwtToken == null) {
filterChain.doFilter(request, response);
return;
}
//认证
try {
//从token中获取信息
UserDetailsImpl userDetails = jwtTokenService.parseJwtToken(jwtToken);
log.info("userDetails:{}", userDetails);
//创建UsernamePasswordAuthenticationToken对象存储认证结果
Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
//UsernamePasswordAuthenticationToken对象存储在上下文对象中
SecurityContextHolder.getContext().setAuthentication(authentication);
//放行
filterChain.doFilter(request, response);
} catch (ServiceException e) {
log.info("token无效");
//响应错误消息(一段json代码)
response.setContentType("application/json; charset=utf-8");
JsonResult<Void> jsonResult = JsonResult.fail(e);
String json = new ObjectMapper().writeValueAsString(jsonResult);
//把json数据写入到页面中
response.getWriter().println(json);
}
}
}
在WebSecurityConfig中配置JWT认证过滤器:(注入和配置)
@Configuration
@EnableWebSecurity //开启登录认证授权
public class WebSecurityConfig {
--->@Autowired
--->private JwtAuthenticationFilter jwtAuthenticationFilter;
//认证管理器
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager(); //返回认证管理器
}
/**
* 设置URL授权规则
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//不需要认证的url
String[] permitAllUrls = {
"/auth/**",
"/error/**"
};
//配置不需要认证的请求路径
http.authorizeHttpRequests((authorization ->
authorization.requestMatchers(permitAllUrls).permitAll()));
//配置需要认证的请求路径
http.authorizeHttpRequests(authorization->
authorization.anyRequest().authenticated());
------->//配置jwt过滤器
------->http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
//禁用跨域防护机制(不推荐)
http.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
JWT登录测试:
POST http://localhost:9081/auth/login
Content-Type: application/json
{
"username": "root",
"password": "1234"
}
(2)处理未认证403
创建JWT认证失败处理器
/**
* @document: JWT认证失败处理器(解决客户端403错误)
* 1、未登录时访问受保护的资源
* 2、认证过程中出现异常
* @Author:SmallG
* @CreateTime:2023/9/27+17:05
*/
@Component
@Slf4j
public class JwtTokenAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e)
throws IOException, ServletException {
log.info("认证失败信息:{}", e);
response.setStatus(HttpServletResponse.SC_OK); //状态码200,不至于让客户看403
response.setContentType("application/json; charset=utf-8");
JsonResult<Void> jsonResult = JsonResult.fail(ServiceCode.ERR_UNAUTHORIZED, "请登录后再访问");
//转换成json,再把json输出出去
String json = new ObjectMapper().writeValueAsString(jsonResult);
response.getWriter().println(json);
}
}
然后在WebSecurityConfig 中注入并且进行配置:
@Configuration
@EnableWebSecurity //开启登录认证授权
public class WebSecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
--->@Autowired
--->private JwtTokenAuthenticationEntryPoint jwtTokenAuthenticationEntryPoint;
//认证管理器
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager(); //返回认证管理器
}
/**
* 设置URL授权规则
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//不需要认证的url
String[] permitAllUrls = {
"/auth/**",
"/error/**"
};
//配置不需要认证的请求路径
http.authorizeHttpRequests((authorization ->
authorization.requestMatchers(permitAllUrls).permitAll()));
//配置需要认证的请求路径
http.authorizeHttpRequests(authorization->
authorization.anyRequest().authenticated());
//配置jwt过滤器
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
------->//配置认证失败处理器
------->http.exceptionHandling().authenticationEntryPoint(jwtTokenAuthenticationEntryPoint);
//禁用跨域防护机制(不推荐)
http.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
(3)获取当前用户信息
注解@AuthenticationPrincipal,可以用来在控制器方法中注入当前已登录系统的用户信息,这个信息实际上是存储在Security Context中的已认证的主体信息UserDetailsImpl,包括了用户的各种信息和权限等。
下面是一个示例控制器方法,演示如何使用认证主体获取当前用户的信息:
@GetMapping("/info")
public JsonResult<AdminStandardVO> getInfo(
@AuthenticationPrincipal UserDetailsImpl userDetails) {
log.debug("开始处理【根据token查询管理员信息】的请求,参数:{}", userDetails);
AdminLoginInfoVO adminLoginInfoVO = userDetails.getAdmin();
AdminStandardVO admin = adminService.getAdminById(adminLoginInfoVO.getId());
log.debug("管理员信息:{}", admin);
return JsonResult.ok(admin);
}
请使HTTP客户端进行测试:
GET http://localhost:9081/auth/info
6、将JWT认证整合到界面
jwt图片也需要认证
(1)配置认证通信接口
在前端项目的 src/api/user.js 文件中,定义了与后端的通信接口,其中包括登录、获取用户信息和退出登录等功能。这些接口用于与后端控制器进行对接,以便实现登录和用户信息获取等操作。
import request from '@/utils/request'
export function login(data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/auth/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/auth/logout',
method: 'post'
})
}
(2)整合前端登录功能
为了满足后端的要求,在用户登录后的每次请求中都必须携带Authorization请求头,需要在前端的 src/utils/request.js 文件中进行相应的设置。这个文件是请求和响应通信协议的拦截器,可以用于设置统一的请求参数和处理统一的响应结果。
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['Authorization'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
在响应拦截器src/utils/request.js中,处理未认证的错误码,当出现未认证的情况时,将用户重定向到登录页面:
// 40100 登录失败,用户名或密码错
// 40300 无权限
// 60000 JWT已过期
// 60100 验证签名失败
// 60200 JWT格式错误
if (res.code === 40100 || res.code === 40300 ||
res.code === 60000 || res.code === 60100 || res.code === 60200) {
// to re-login
MessageBox.confirm('您已注销,点击取消停留在此页面,或重新登录', '确认登录', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
修改认证,添加图像认证,在jwt认证过滤器中添加内容:
/**
* @document: JWT认证过滤器
* 在发送请求的过程中 一般把token放在请求头中
* @Author:SmallG
* @CreateTime:2023/9/27+16:36
*/
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenService jwtTokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取请求头
String jwtToken = request.getHeader("Authorization");
log.info("jwtToken:{}", jwtToken);
------->新加入开始
//Spring Security 6.0后 对于图片需要认证
//从客户端的Cookie中获取token Cookie的名字是vue_admin_template_token
String cookieName = "vue_admin_template_token";
if (jwtToken == null) {
String cookieHeader = request.getHeader("Cookie");//获取请求中的Cookie
log.info("Cookie:{}", cookieHeader);
if (cookieHeader != null) {
String[] cookies = cookieHeader.split(";");
for (String cookie : cookies) {
if (cookie.trim().startsWith(cookieName + "=")) {
//截取从这个名字到最后的字符串
jwtToken = cookie.substring(cookieName.length() + 1);
break; //找到后break,下次就不用做这个循环了
}
}
}
}
------->新加入结束
//不需要认证的请求,直接访问 例如登录、注册、验证码等
//如果还是空那就是没登录
if (jwtToken ==null){
filterChain.doFilter(request,response);
return;
}
//认证
try {
//从token中获取信息
UserDetailsImpl userDetails = jwtTokenService.parseJwtToken(jwtToken);
log.info("userDetails:{}", userDetails);
//创建UsernamePasswordAuthenticationToken对象存储认证结果
Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
//UsernamePasswordAuthenticationToken对象存储在上下文对象中
SecurityContextHolder.getContext().setAuthentication(authentication);
//放行
filterChain.doFilter(request, response);
} catch (ServiceException e) {
log.info("token无效");
//响应错误消息(一段json代码)
response.setContentType("application/json; charset=utf-8");
JsonResult<Void> jsonResult = JsonResult.fail(e);
String json = new ObjectMapper().writeValueAsString(jsonResult);
//把json数据写入到页面中
response.getWriter().println(json);
}
}
}
重启项目后可正常登录