• JSD-2204-处理登录成功的管理的权限列表-前后端分离-Day13


    1.处理登录成功的管理的权限列表

    目前,存入到Security上下文中的认证信息(Authentication对象)并不包含有效的权限信息(目前是个假信息),为了后续能够判断用户的权限,需要:

    • 当认证(登录)成功后,取出管理员的权限,并将其存入到JWT数据中
    • 后续的请求中的JWT应该已经包含权限,则可以从JWT中解析出权限信息,并存入到认证信息(Authentication对象)中
    • 在操作过程中,应该先将权限列表转换成JSON再存入到JWT中,在解析JWT时,得到的权限信息也是一个JSON数据,需要将其转换成对象才能继续使用

    关于JSON格式的转换,有许多工具都可以实现,例如:fastjson

    1. <dependency>
    2. <groupId>com.alibabagroupId>
    3. <artifactId>fastjsonartifactId>
    4. <version>1.2.75version>
    5. dependency>

    AdminServiceImpl处理登录时,当认证成功时,需要从认证结果中取出权限列表,转换成JSON字符串,并存入到JWT中:

    1. // 原有其它代码
    2. Collection authorities = loginUser.getAuthorities();
    3. log.debug("认证结果中的权限列表:{}", authorities);
    4. String authorityListString = JSON.toJSONString(authorities); // 【重要】将权限列表转换成JSON格式,用于存储到JWT中
    5. // 生成JWT时的Claims相关代码
    6. claims.put("authorities", authorityListString);
    7. log.debug("生成JWT,向JWT中存入authorities:{}", authorityListString);
    8. // 原有其它代码

    然后,在JWT过滤器中,当成功的解析JWT时,应该获取权限列表的JSON字符串,并将其转换为认证对象要求的格式(Collection):

    1. // 原有其它代码
    2. Object authorityListString = claims.get("authorities");
    3. log.debug("从JWT中解析得到authorities:{}", authorityListString);
    4. // 准备Authentication对象,后续会将此对象封装到Security的上下文中
    5. List authorities = JSON.parseArray(
    6. authorityListString.toString(), SimpleGrantedAuthority.class);
    7. Authentication authentication = new UsernamePasswordAuthenticationToken(
    8. username, null, authorities);
    9. // 原有其它代码

    完成后,启动项目,正常登录,在服务器端的控制台可以看到相关日志,将显示存入到Security上下文的认证信息中包含权限列表。

    1.1使用Security框架检查权限

    首先,需要在Security的配置类上开启全局的在方法上检查权限:

    1. // 其它原有注解
    2. @EnableGlobalMethodSecurity(prePostEnabled = true) // 新增
    3. public class SecurityConfiguration ... ...

    然后,在控制器类中处理请求的方法上使用@PreAuthorize注解检查权限:

    1. // 其它原有注解
    2. @PreAuthorize("hasAuthority('/ams/admin/update')") // 新增
    3. public JsonResult ...

    以上注解表示:必须具有/ams/admin/update权限才允许向此路径提交请求。

    提示:Security会根据上下文中的权限列表进行对比,来检查当前登录的用户是否具有此权限。

    1.2自定义UserDetails

    Security使用UserDetails接口类型的对象表示需要认证的用户、认证结果中的Principal,但是,Security框架中UserDetails接口的实现类User中并不包含id及其它个性化属性,则可以自定义类进行扩展:

    1. package cn.tedu.csmall.passport.security;
    2. import lombok.EqualsAndHashCode;
    3. import lombok.Getter;
    4. import lombok.Setter;
    5. import lombok.ToString;
    6. import org.springframework.security.core.GrantedAuthority;
    7. import org.springframework.security.core.userdetails.User;
    8. import java.util.Collection;
    9. @Setter
    10. @Getter
    11. @EqualsAndHashCode
    12. @ToString(callSuper = true)
    13. public class AdminDetails extends User {
    14. /**
    15. * 管理员id
    16. */
    17. private Long id;
    18. public AdminDetails(String username, String password, boolean enabled,
    19. Collection authorities) {
    20. super(username, password, enabled,
    21. true, true, true,
    22. authorities);
    23. }
    24. }

    接下来,在UserDetailsServiceImplUserDetails loadUserByUsername(String username)方法的实现中,使用自定义的AdminDetails作为此方法的返回结果类型:

    1. package cn.tedu.csmall.passport.security;
    2. import cn.tedu.csmall.passport.mapper.AdminMapper;
    3. import cn.tedu.csmall.passport.pojo.vo.AdminLoginInfoVO;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.security.core.authority.SimpleGrantedAuthority;
    7. import org.springframework.security.core.userdetails.User;
    8. import org.springframework.security.core.userdetails.UserDetails;
    9. import org.springframework.security.core.userdetails.UserDetailsService;
    10. import org.springframework.security.core.userdetails.UsernameNotFoundException;
    11. import org.springframework.stereotype.Service;
    12. import java.util.ArrayList;
    13. import java.util.List;
    14. @Slf4j
    15. @Service
    16. public class UserDetailsServiceImpl implements UserDetailsService {
    17. @Autowired
    18. private AdminMapper adminMapper;
    19. @Override
    20. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    21. log.debug("根据用户名【{}】从数据库查询用户信息……", s);
    22. // 调用AdminMapper对象,根据用户名(参数值)查询管理员信息
    23. AdminLoginInfoVO loginInfo = adminMapper.getLoginInfoByUsername(s);
    24. // 判断是否查询到有效结果
    25. if (loginInfo == null) {
    26. // 根据用户名没有找到任何管理员信息
    27. String message = "登录失败,用户名不存在!";
    28. log.warn(message);
    29. throw new UsernameNotFoundException(message);
    30. }
    31. log.debug("根据用户名【{}】从数据库查询到有效的用户信息:{}", s, loginInfo);
    32. // 从查询结果中找出权限信息,转换成Collection
    33. List permissions = loginInfo.getPermissions(); // /ams/admin/delete
    34. List authorities = new ArrayList<>();
    35. for (String permission : permissions) {
    36. authorities.add(new SimpleGrantedAuthority(permission));
    37. }
    38. // 返回AdminDetails类型的对象
    39. AdminDetails adminDetails = new AdminDetails(
    40. loginInfo.getUsername(), loginInfo.getPassword(),
    41. loginInfo.getEnable() == 1, authorities);
    42. adminDetails.setId(loginInfo.getId());
    43. log.debug("即将向Spring Security返回UserDetails:{}", adminDetails);
    44. return adminDetails;
    45. }
    46. }

    后续,在AdminServiceImpl处理登录时,当认证通过,在认证结果中的Principal就是AdminDetails类型的。

    所以,当认证通过后,可以将认证结果中的Principal取出,强制转换为AdminDetails类型,并取出id值,用于生成JWT数据:

    1. // 原有其它代码
    2. // 处理认证结果
    3. AdminDetails loginUser = (AdminDetails) authenticateResult.getPrincipal();
    4. log.debug("认证结果中的管理员id:{}", loginUser.getId());
    5. log.debug("认证结果中的用户名:{}", loginUser.getUsername());
    6. Collection authorities = loginUser.getAuthorities();
    7. log.debug("认证结果中的权限列表:{}", authorities);
    8. // 【重要】将权限列表转换成JSON格式,用于存储到JWT中
    9. String authorityListString = JSON.toJSONString(authorities);
    10. // 生成JWT
    11. String secretKey = "nmlfdasfdsaurefuifdknjfdskjhajhef";
    12. // 准备Claims
    13. Map claims = new HashMap<>();
    14. claims.put("id", loginUser.getId());
    15. claims.put("username", loginUser.getUsername());
    16. claims.put("authorities", authorityListString);
    17. log.debug("生成JWT,向JWT中存入id:{}", loginUser.getId());
    18. log.debug("生成JWT,向JWT中存入username:{}", loginUser.getUsername());
    19. log.debug("生成JWT,向JWT中存入authorities:{}", authorityListString);
    20. // 原有其它代码

    至此,当登录成功后,生成的JWT中将包含id

    接下来,在JWT过滤器中,解析JWT时,就可以解析得到id的值:

    1. // 尝试解析JWT
    2. String secretKey = "nmlfdasfdsaurefuifdknjfdskjhajhef";
    3. Claims claims = Jwts.parser().setSigningKey(secretKey)
    4. .parseClaimsJws(jwt).getBody();
    5. Long id = claims.get("id", Long.class);
    6. String username = claims.get("username", String.class);
    7. String authorityListString = claims.get("authorities", String.class);
    8. log.debug("从JWT中解析得到id:{}", id);
    9. log.debug("从JWT中解析得到username:{}", username);
    10. log.debug("从JWT中解析得到authorities:{}", authorityListString);

    解析得到的idusername都应该封装到认证对象中,进而将认证对象存入到Security上下文中,由于UsernamePasswordAuthenticationToken中的Principal是Object类型的,表示“当事人”,即“当前成功登录的用户”,所以,可以自定义数据类型,封装idusername,并将封装后的对象存入到UsernamePasswordAuthenticationToken中:

    1. package cn.tedu.csmall.passport.security;
    2. import lombok.Data;
    3. import java.io.Serializable;
    4. /**
    5. * 用于保存到Security上下文中的、当前登录的管理员信息(不包含权限信息)
    6. */
    7. @Data
    8. public class LoginPrincipal implements Serializable {
    9. /**
    10. * 当事人id
    11. */
    12. private Long id;
    13. /**
    14. * 当事人用户名
    15. */
    16. private String username;
    17. }
    1. // 准备Authentication对象,后续会将此对象封装到Security的上下文中
    2. LoginPrincipal loginPrincipal = new LoginPrincipal();
    3. loginPrincipal.setId(id);
    4. loginPrincipal.setUsername(username);
    5. List authorities = JSON.parseArray(
    6. authorityListString, SimpleGrantedAuthority.class);
    7. Authentication authentication = new UsernamePasswordAuthenticationToken(
    8. loginPrincipal, null, authorities);

    至此,每次客户端携带有效的JWT提交请求时,都可以从中解析得到idusername,这些数据也会保存到Security上下文中,则在任何控制器处理请求的方法上,可以添加@AuthenticationPrincipal LoginPrincipal loginPrincipal,即可注入Security上下文中的LoginPrincipal对象,则可以获取到当事人(当前成功登录的用户)的idusername,例如:

    1. @ApiOperation("查询角色列表")
    2. @ApiOperationSupport(order = 401)
    3. @GetMapping("")
    4. public JsonResult> list(
    5. @ApiIgnore @AuthenticationPrincipal LoginPrincipal loginPrincipal) {
    6. log.debug("准备处理【查询角色列表】的请求");
    7. log.debug("当前登录的用户(当事人)的id:{}", loginPrincipal.getId());
    8. log.debug("当前登录的用户(当事人)的用户名:{}", loginPrincipal.getUsername());
    9. List list = roleService.list();
    10. return JsonResult.ok(list);
    11. }

    提示:以上请求参数还添加了@ApiIgnore注解,其作用是在Knife4j的API文档中忽略此参数,否则,还会在Knife4j文档中显示LoginPrincipal对应的参数。

    2.通过前端界面实现登录

    1. {
    2. "state": 20000,
    3. "data": "eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9.eyJpZCI6MSwiZXhwIjoxNjYxMjM5MzMxLCJhdXRob3JpdGllcyI6Ilt7XCJhdXRob3JpdHlcIjpcIi9hbXMvYWRtaW4vZGVsZXRlXCJ9LHtcImF1dGhvcml0eVwiOlwiL2Ftcy9hZG1pbi9yZWFkXCJ9LHtcImF1dGhvcml0eVwiOlwiL2Ftcy9hZG1pbi91cGRhdGVcIn0se1wiYXV0aG9yaXR5XCI6XCIvcG1zL3Byb2R1Y3QvZGVsZXRlXCJ9LHtcImF1dGhvcml0eVwiOlwiL3Btcy9wcm9kdWN0L3JlYWRcIn0se1wiYXV0aG9yaXR5XCI6XCIvcG1zL3Byb2R1Y3QvdXBkYXRlXCJ9XSIsInVzZXJuYW1lIjoicm9vdCJ9.bLrqPBNVVC9nQejqhGeUhr7QETbVSxoZZaZ-YSK6O6o"
    4. }
    1. {
    2. "state": 50000,
    3. "message": "程序运行过程中出现意外错误,请联系系统管理员!"
    4. }
  • 相关阅读:
    “禁止互撕”新规第二天,热搜把#章子怡“怒怼”网友#推上了榜一
    Flutter Json解析工具
    路由Import&Control
    【定语从句练习题】限制性与非限制性
    elasticsearch查询之keyword字段的查询打分控制
    深入分析LinkedHashMap
    ARTS打卡第三周
    C++20开发工程师 系列 笔记环境搭建之国内源(2022/11/30)
    vue处理边界情况
    eclipse Maven配置
  • 原文地址:https://blog.csdn.net/TheNewSystrm/article/details/126253605