记录SpringSecurity jwt问题

认证、授权、鉴权和权限控制


0.jwt 加签->验签 加签参数中包含:头部()负载(用户名,创建时间)验签:客户端传值与服务端算一致,别人篡改什么?用户名? 验签成功标识?失败提示什么?@篡改 用户名。1.token 错误,根据token 解析荷载是会报错,其实这里应该把异常抛出,token错误!
(业务:实际报异常:暂未登录或token已经过期,因为取了用户信息,取不到报错。不取用户不会报错,即便token错误)
 验签:就是根据token 反解jwt,无token或token被篡改,回报异常,说明验签失败!
 /**
     * 从token中获取JWT中的负载
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            LOGGER.info("JWT格式验证失败:{}", token, e);
        }
        return claims;
    }
    
1.注册后,用户信息怎么进入security的? @注册后没有存用户信息,是登录时,根据用户名查询数据库,然后存security。
2.权限怎么进去的   SecurityConfig configure 在这里从数据库查询权限,还有用户信息保存了,保存到哪里了?->AdminUserDetails 包含用户信息,权限信息,并提供对外接口
2.1 用户权限信息包含两部分 1.角色权限表 2.用户权限表,用户权限表数据怎么进去的(暂时业务没有用到)? @登录时,查数据库表,取权限赋值??不是登录时还是直接取UserDetailsService!!!。总结:登录赋值,调接口取值
3.sysUser 用户类 联系 @同一个
4.jwt 生成token原理
普通token只有过期时间与用户信息,而且会被伪造,jwt包含签名,不会被伪造。更像是一个签名
5.用户登录成功后,除了token,还有菜单权限数据,还有一个权限列表,这是个啥,权限值吗,有什么?鉴权吗? @前者只有菜单,后者包含所有,不只是权限,完整权限数据。应该不是控制权限,是控制数据是否展示。控制权限通过security注解
   Map<String, Object> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("tokenHead", tokenHead);
        tokenMap.put("authorities", SecurityUtils.getAuthentication().getAuthorities()); @包含所有权限
        tokenMap.put("menuList", sysUserService.getMenuPermissionList(SecurityUtils.getUser().getId())); @只有菜单,没有按钮
    tokenMap.put("uid", SecurityUtils.getUser().getId());
    
    
6.根据用户名,获取用户信息密码,权限原理,@ 是登录成功后,缓存了!!! 用户名哪里来:根据token获取,jwt中获取,因为登录生产jwt时,存入了用户名。
7.jwt 自定义鉴权过滤器 做什么 1.用户名是否正确 security中获取与token中是否一直,token中是当前登录人,为什么? @防止A登录成功,拿B的token取调接口,扩大权限,显然只判断有效期不行,2.token是否过期 
8.UserDetailsService 根据用户名取用户信息,调用场景 登录时 1.登录取密码校验 2.jwt过滤器 验证用户名,token,最终从缓存中获取。但是入口在AdminUserDetails,说明在某时候数据进入了缓存 EhCache,进程内缓存,不支持分布式  @关于用户信息登录时,查库存security,其他接口直接取。
9.ums_admin_permission_relation 用户权限表中type=1含义 数据入口在->给用户分配+-权限,含义? @ 应该是增加删除权限,每次都是权限全量,与用户角色权限中权限比较,多:标记为+,少:标记为-,记的意义应该是保存记录
10.SecurityContext 取权限 赋值权限,但是权限值也是从UserDetails中获取,??此处哪里给?何时给?看上面:第8条
11.解决用户登录查库问题
首先,登录时是要查库,取用户与权限,只不过后面调接口时不必再查询数据库。
方式1.实现接口:UserDetailsService,重写方法,查询库把用户及权限信息写入,UserDetail接口。
方式2.重写配置类SecurityConfig 中 抽象类WebSecurityConfigurerAdapter userDetailsService方法
12.用户登录流程

虽然jwt配置了过滤登录请求,但是还是会走jwt过滤器(没有token 不会走jwt 认证及鉴权),然后到登录接口,根据用户名无法获得用户信息,说明用户名错误,抛出用户名密码错误。
如果密码错误,取到用户信息后,再比较密码,不正确抛出。那问题来了,为什么能获取用户信息???注册时缓存吗?@@@@查询数据库的!!每次都查询吗?应该是,之前有人说,不要每次查看?? @@@登录时,要查,调接口不再需要
13.权限来源于哪?UsernamePasswordAuthenticationToken 赋值过两次权限,都是从数据库查询用户权限,然后给到token。场景:1.登录 2.jwt过滤器(调用非登录接口时)。应该没有缓存,都是实时取表  @ 查库只有登录时,jwt过滤器也是取登录时赋值。jwt过滤器赋值权限,应该是注解鉴权时,跟这里比较。登录时,给了权限,为什么这里还要给??
   if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    LOGGER.info("authenticated user:{}", username);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
                
登录成功后,把信息赋值UserDetail接口(重写),后面调用其他接口,可以直接获取用户名。
正常的应该只需要登录时查询数据库,取出权限,后面调接口从缓存中获取,这里就涉及一个缓存刷新问题??? @@@应该是这样。

14.当未登录或者token失效,(无token或错误,无法根据token反解jwt,报异常,到下一个过滤链,进入自定义异常)访问接口时,自定义的返回结果  RestAuthenticationEntryPoint 调用接口未带token,来这里,{
  "code": 401,
  "data": "Full authentication is required to access this resource",
  "msg": "暂未登录或token已经过期"
}

15.访问接口时,可获取当前用户所有权限,具体怎么校验有没有权限,登录后,会返回菜单和权限,这时候前端会处理,没有就看不到菜单或者按钮。但是通过接口怎么拦截?@通过security注解,@PreAuthorize 在每一个接口上加权限。

    @PreAuthorize("hasAuthority('product')")
    @GetMapping("myRoom")
    @ApiOperation("我的直播间")
    public BaseResult<ResTextLiveProgramVo> myChatRoom() {
    return textLiveProgrammeService.myChatRoom();
    }

16.自定义:登录失败处理器,两种方式
@Component
//public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
public class RestAuthenticationEntryPoint implements AuthenticationFailureHandler {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSON.toJSONString(BaseResult.unauthorized(authException.getMessage())));
        response.getWriter().flush();
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                    AuthenticationException e) throws IOException, ServletException {
        
    }
}

实际代码中并没有采用以上任意一种,而是在登录时,捕获了用户或密码错误异常,统一返回报错。

16.验证权限
无权限报错: 不允许访问
在接口添加权限限制 @PreAuthorize("hasAuthority('product') 
问题
1.没走自定义无权限,直接报:不允许访问
2.原理是什么,异常从哪抛出的
3.不手动添加权限限制,直接调接口,怎么实现鉴权 @ 每一个接口都是手动给权限

17.登出 
jwt 在有效期一直可用,无失效逻辑。
token:之前都是存在redis有失效机制。
需要做两件事
1.前端删除token
2.自定义登录处理器,并在security中配置,把请求头token设置为空。
登出过滤器
@Component
public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
    if (Objects.nonNull(authentication)) {
        new SecurityContextLogoutHandler().logout(request, response, authentication);
    }
    response.setContentType("application/json;charset=UTF-8");
    ServletOutputStream outputStream = response.getOutputStream();
    response.setHeader("Authorization", "");
    BaseResult result = BaseResult.success("");
    outputStream.write(JSON.toJSONString(result).getBytes("UTF-8"));
    outputStream.flush();
    outputStream.close();
    }
}


18 SecurityConfig jwt直接关联
在security配置类中加 入jwt过滤器
1.创建实例
    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
        return new JwtAuthenticationTokenFilter();
    }
2.添加JWT filter
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

19.jwt 存在问题
https://blog.csdn.net/hanxiaotongtong/article/details/103347063

20.token刷新
1.接口请求请求头带 token, Authorization:token
2.刷新token,只是在jwt生产token中,更新了创建时间

21.jwt作用 
1.认证 根据判断用户名,密码  jwt加签存了用户信息,验签验证身份
2.鉴权,权限不足 @主要由security完成,只是利用了jwt过滤器

22.spring Security 如何保存权限信息

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
        null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);

涉及到上下文
public interface SecurityContext extends Serializable {
    Authentication getAuthentication();

    void setAuthentication(Authentication var1);
}

  SecurityContextHolder.getContext().setAuthentication(authentication);
  把权限信息给到给到上下问Context,两个地方,登录时,jwt验证后。登录后,已经把用户权限给context,为什么调接口还要赋值
  
  if (username != null && SecurityContextHolder.getContext().getAuthentication() == null),因为jwt判断为null才赋值,为什么为空
 
23.每次登录成功后,把用户及权限信息,保存到上下文,怎么实现用户隔离 

参考文档:
http://www.cocoachina.com/articles/42621
https://zhengkai.blog.csdn.net/article/details/96290686

赋值权限
AdminUserDetails(自定义认证主体,包含用户信息及权限信息类。认证:及校验密码,成功后,设置创建一个实现了 Authentication接口的对象,给上下文)-> UserDetails -> UsernamePasswordAuthenticationToken ->Authentication->SecurityContext->-ThreadLocal(又保存到httpSession,取的时候从这里)-@> SecurityContextHolder


SecurityContextHolder 是如何存储 SecurityContext 的。 ThreadLocalSecurityContextHolderStrategy 怎么存储到ThreadLocal
https://www.it610.com/article/1281109153997144064.htm
https://www.bbsmax.com/A/gVdnpGr1JW/
https://blog.csdn.net/liuyanglglg/article/details/104742799
https://blog.csdn.net/weixin_34366546/article/details/87994814?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.no_search_link&spm=1001.2101.3001.4242

【FilterChain】【HttpSession】【SecurityContextPersistenceFilter】
SecurityContextPersistenceFilter:
该Filter的作用主要是创建一个空的SecurityContext(如果session中没有SecurityContext实例),然后持久化到session中


HttpSessionSecurityContextRepository 
context 与httpsessin 绑定,为什么存在httpSession ,为什么也要保存到ThreadLocal
https://cloud.tencent.com/developer/article/1361030

https://blog.didispace.com/xjf-spring-security-4/ 比较全的源码分析
https://blog.didispace.com/xjf-spring-security-3/
SecurityContextPersistenceFilter 两个主要职责:请求来临时,创建SecurityContext安全上下文信息,请求结束时清空
请求来临时 指调用接口吗,请求结束接口执行完成吗?创建context是不是应该登录时创建。

https://www.bbsmax.com/A/gVdnpGr1JW/
https://www.it610.com/article/1281109153997144064.htm
https://zhengkai.blog.csdn.net/article/details/96290686
http://www.cocoachina.com/articles/42621
https://www.shuzhiduo.com/A/QW5YV87Gdm/
https://www.cnblogs.com/felordcn/p/12142491.html
https://blog.csdn.net/andy_zhang2007/article/details/91955225
https://my.oschina.net/u/2518341/blog/1982933
https://blog.csdn.net/qq_35067322/article/details/103209951
https://www.cnblogs.com/longfurcat/p/10293819.html
https://cloud.tencent.com/developer/article/1361030
https://blog.csdn.net/liuyanglglg/article/details/104742799
https://blog.csdn.net/weixin_34366546/article/details/87994814?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.no_search_link&spm=1001.2101.3001.4242
https://blog.csdn.net/fengyilin_henu/article/details/84916822
https://www.cnblogs.com/longfurcat/p/10574734.html


1.使用security 依赖,配置(方法级别)


jwt:
  tokenHeader: Authorization
  secret: mySecret
  # 60*60*24
  expiration: 604800  s /60/60 168  7天有效期
  tokenHead: Bearer
  
  

UserDetails:构建Authentication对象必须的信息,可以自定义,可能需要访问DB得到

SpringSecurity JWT
认证、授权、鉴权和权限控制


1.JWT作用:身份认证(token生成与校验)
生成与校验场景:生成:用户登录;校验:用户进行操作时,走JWT过滤器,从请求头中获取token,根据token反解JWT中的负载。
成功失败标志:从token中反解JWT中的负载时,如果异常,说明认证失败,无异常则认证成功

2.SpringSecurity:授权,鉴权
授权:创建用户选择权限及管理员分配权限,需要权限控制的方法上加注解:@PreAuthorize("hasAuthority('sys:role:update')")
SpringSecurity如何管理权限:接口请求走JWT过滤器,实时查数据库取用户基本信息与权限信息。
存在哪里:
怎么获取:
https://blog.csdn.net/u012702547/article/details/89629415
https://blog.csdn.net/weixin_39915815/article/details/111038973?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-6.nolandingword2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-6.nolandingword2

上一篇:FastAPI + Vue 实现 OAuth2 的 jwt token 登录验证


下一篇:jwt内置签发方法修改返回格式 drf_jwt签发认证源码 自定义用户表签发token