SpringSecurity整合jwt权限认证的全流程讲解

JWT

本文代码截取自实际项目。

jwt(Json Web Token),一个token,令牌。

简单流程:

用户登录成功后,后端返回一个token,也就是颁发给用户一个凭证。之后每一次访问,前端都需要携带这个token,后端通过token来解析出当前访问对象。

优点

1、一定程度上解放了后端,后端不需要再记录当前用户是谁,不需要再维护一个session,节省了开销。

2、session依赖于cookie,某些场合cookie是用不了的,比如用户浏览器cookie被禁用、移动端无法存储cookie等。

缺点

1、既然服务器通过token就可以知道当前用户是谁,说明其中包含了用户信息,有一定的泄露风险。

2、因为是无状态的,服务器不维持会话,那么每一次请求都会重新去数据库读取权限信息,性能有一定影响。

(如果想提高性能,可以将权限数据存到redis,但是如果用redis,就已经失去了jwt的优点,直接用普通的token+redis即可)

3、只能校验token是否正确,通过设定过期时间来确定其有效性,不可以手动注销。

先说怎么做,再说为什么。

代码

依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <security.version>2.3.3.RELEASE</security.version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <jwt.version>0.9.1</jwt.version>
        </dependency>

jwt工具类

public class JwtTokenUtils implements Serializable {
    private static final long serialVersionUID = -3369436201465044824L;
    //生成token
    public static String createToken(String username) {
        return Jwts.builder().setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis()+ Constants.Jwt.EXPIRATION))
                .signWith(SignatureAlgorithm.HS512, Constants.Jwt.KEY).compressWith(CompressionCodecs.GZIP).compact();
    }

    //获取用户名
    public static String getUserName(String token) {
        return Jwts.parser().setSigningKey(Constants.Jwt.KEY).parseClaimsJws(token).getBody().getSubject();
    }
}

涉及常量

public interface Constants {
    public interface Jwt{
        /**
         * 密钥
         */
        String KEY = "123123";
        /**
         * 过期时间
         */
        long EXPIRATION = 7200000;
        /**
         * 请求头
         */
        String TOKEN_HEAD = "Authorization";
    }
}

实体类和Service

为了减少对实体类的入侵,我又定义了一个对象

原实体类

/**
 * 用户信息
 *
 */
@Data
@TableName("tb_user_info")
public class UserInfo implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 主键id
	 */
	@TableId(type = IdType.AUTO)
	private Integer id;
	/**
	 * 登陆账号
	 */
	private String loginName;
	/**
	 * 显示名
	 */
	private String dispName;
	/**
	 * 密码
	 */
	private String password;
	/**
	 * 状态,1正常,2禁用
	 */
	private Byte status;
	/**
	 * 创建人
	 */
	@TableField(fill = FieldFill.INSERT)
	private Integer createBy;
	/**
	 * 更新人
	 */
	@TableField(fill = FieldFill.INSERT_UPDATE)
	private Integer updateBy;
	/**
	 * 创建时间
	 */
	@TableField(fill = FieldFill.INSERT)
	private Date createTime;
	/**
	 * 更新时间
	 */
	@TableField(fill = FieldFill.INSERT_UPDATE)
	private Date updateTime;
	/**
	 * 1正常, 0 删除
	 */
	@TableLogic
	private Byte deleted;
}

定义对象

@Data
public class UserSecurity implements UserDetails {
    private static final long serialVersionUID = -6777760550924505136L;
    private UserInfo userInfo;
    private List<String> permissionValues;
    public UserSecurity(UserInfo userInfo) {
        this.userInfo = userInfo;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        for(String permissionValue : permissionValues) {
            if(StringUtils.isEmpty(permissionValue)) continue;
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
            authorities.add(authority);
        }

        return authorities;
    }

    @Override
    public String getPassword() {
        return userInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return userInfo.getLoginName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

service

@Service
public class UserInfoServiceImpl implements  UserInfoService, UserDetailsService {

    @Autowired
    UserInfoMapper userInfoMapper;

    @Autowired
    PermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("login_name", username);
        UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
        List<String> rights = permissionService.listPermissions(username);
        UserSecurity userSecurity = new UserSecurity(userInfo);
        userSecurity.setPermissionValues(rights);
        return userSecurity;
    }
}

配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserInfoServiceImpl userInfoService;

    @Autowired
    JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter;

    @Autowired
    TokenLoginFilter tokenLoginFilter;

    @Autowired
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userInfoService).passwordEncoder(passwordEncoderBean());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .and().csrf().disable()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/hello").permitAll()
                .antMatchers("/swagger-ui.html").permitAll()
                .antMatchers("/webjars/**").permitAll()
                .antMatchers("/swagger-resources/**").permitAll()
                .antMatchers("/v2/*").permitAll()
                .antMatchers("/csrf").permitAll()
                .antMatchers("/doc.html").permitAll()
                .antMatchers(HttpMethod.OPTIONS, "/**").anonymous()
                .anyRequest().authenticated()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .addFilterAt(tokenLoginFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(jwtAuthorizationTokenFilter, TokenLoginFilter.class).httpBasic()
                ;
    }

    @Bean
    public PasswordEncoder passwordEncoderBean() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}

过滤器

一个负责登录

一个负责鉴权

@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {

    @Autowired
    PermissionService permissionService;

    @Autowired
    UserInfoServiceImpl userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //获取当前认证成功用户权限信息
        UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
        //判断如果有权限信息,放到权限上下文中
        if (authRequest != null) {
            SecurityContextHolder.getContext().setAuthentication(authRequest);
        }

        chain.doFilter(request, response);

    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        //从header获取token
        String token = request.getHeader(Constants.Jwt.TOKEN_HEAD);
        if (token != null) {
            //从token获取用户名
            String loginName = JwtTokenUtils.getUserName(token);
            //获取权限列表
            UserInfo userInfo = userDetailsService.selectByLoginName(loginName);
            List<String> permissions = permissionService.listPermissions(loginName);
            Collection<GrantedAuthority> authority = new ArrayList<>();
            for (String permissionValue : permissions) {
                SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);
                authority.add(auth);
            }
            return new UsernamePasswordAuthenticationToken(userInfo, token, authority);
        }
        return null;
    }
}
Component
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    public TokenLoginFilter() {
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login","POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //获取表单提交数据
        try {
            UserInfo user = new ObjectMapper().readValue(request.getInputStream(), UserInfo.class);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getLoginName(), user.getPassword(),null);
            return super.getAuthenticationManager().authenticate(authenticationToken);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        UserSecurity userSecurity = (UserSecurity) authResult.getPrincipal();
        String token = JwtTokenUtils.createToken(userSecurity.getUsername());
        ResponseUtils.out(response, R.ok(token));
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        ResponseUtils.out(response, R.fail(ServiceError.LOGIN_FAIL));
    }

    @Autowired
    @Override
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }
}

异常处理

登陆失败异常处理,自定义返回类型

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ResponseUtils.out(response, R.fail(ServiceError.AUTHENTICATION_FAIL));
    }
}

至于权限和角色,得看具体的业务,我这边就不贴出来了。

流程分析

总体分析

代码肯定不是凭空出来的,必定有章可循。

首先看网上找的这个图:

最前面两个过滤器:密码检验、权限认证。(顺序很重要,鉴权之前总得给人家输入账号密码的机会吧)

想一下传统的session会话模式,登录成功后,服务器维护了session,浏览器得cookie里存放了sessionId,用户访问的时候浏览器自动带上sessionId。

放在以前,服务器端的工作都是是框架帮我们做了(原生session来自于tomcat)。

换成jwt后,需要前端自己去存储token,后台自己来解析token。

但是流程还是一样的,用户登录、获取token、携带token、校验token。。。。。。

登录校验过滤器分析

分析完流程后,就需要看看,如果不用jwt,框架自身是如何实现的。

找到UsernamePasswordAuthenticationFilter这个类

可以发现这个类主要是用来检验密码生成,凭证的。那我们只要继承这个UsernamePasswordAuthenticationFilter类,重写attemptAuthentication,就可以实现登录校验功能。

所以我们的代码是这样的:

权限认证过滤器分析

需要看一下BasicAuthenticationFilter的源码

其中的关键方法:

思考一下,我们如果需要用户信息,会从哪里取?

自然是请求头,前端访问的时候,会把token放在请求头。

取得token后,就要开始解析token了。token正确,将认证信息以及权限信息注入,token不正确则可以抛出异常。这就是我们第二个过滤器的由来

一个地方需要注意一下:

这边的userInfo,就是对应的principal

SecurityContextHolder.getContext().getAuthentication().getPrincipal();

比如这么用:

@Component
public class SecurityUtils {
    /**
     * 获取当前用户信息
     * @return 用户信息
     */
    public UserInfo getCurrentUser() {
        return (UserInfo) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

这里的UserInfo对象,就是principal。

如果你上面放的不对象,而是用户名或者id,那么获取principal的时候,自然是对应的用户名或者id了。

种瓜得瓜,种豆得豆。

配置文件

截取核心片段加点注释。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringSecurity+JWT实现前后端分离的使用详解

    创建一个配置类 SecurityConfig 继承 WebSecurityConfigurerAdapter package top.ryzeyang.demo.common.config; import org.springframework.context.annotation.Bean; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework

  • Springboot+SpringSecurity+JWT实现用户登录和权限认证示例

    如今,互联网项目对于安全的要求越来越严格,这就是对后端开发提出了更多的要求,目前比较成熟的几种大家比较熟悉的模式,像RBAC 基于角色权限的验证,shiro框架专门用于处理权限方面的,另一个比较流行的后端框架是Spring-Security,该框架提供了一整套比较成熟,也很完整的机制用于处理各类场景下的可以基于权限,资源路径,以及授权方面的解决方案,部分模块支持定制化,而且在和oauth2.0进行了很好的无缝连接,在移动互联网的授权认证方面有很强的优势,具体的使用大家可以结合自己的业务场景进行选

  • 解析SpringSecurity+JWT认证流程实现

    纸上得来终觉浅,觉知此事要躬行. 楔子 本文适合:对Spring Security有一点了解或者跑过简单demo但是对整体运行流程不明白的同学,对SpringSecurity有兴趣的也可以当作你们的入门教程,示例代码中也有很多注释. 本文代码:码云地址  GitHub地址 大家在做系统的时候,一般做的第一个模块就是认证与授权模块,因为这是一个系统的入口,也是一个系统最重要最基础的一环,在认证与授权服务设计搭建好了之后,剩下的模块才得以安全访问. 市面上一般做认证授权的框架就是shiro和Spri

  • SpringSecurity整合Jwt过程图解

    这篇文章主要介绍了SpringSecurity整合Jwt过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.创建项目并导入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>

  • SpringSecurity Jwt Token 自动刷新的实现

    功能需求 最近项目中有这么一个功能,用户登录系统后,需要给 用户 颁发一个 token ,后续访问系统的请求都需要带上这个 token ,如果请求没有带上这个 token 或者 token 过期了,那么禁止访问系统.如果用户一直访问系统,那么还需要自动延长 token 的过期时间. 功能分析 1.token 的生成 使用现在比较流行的 jwt 来生成. 2.token 的自动延长 要实现 token 的自动延长,系统给用户 颁发 一个 token 无法实现,那么通过变通一个,给用户生成 2个 t

  • springboot+jwt+springSecurity微信小程序授权登录问题

    场景重现:1.微信小程序向后台发送请求 --而后台web采用的springSecuriry没有token生成,就会拦截请求,,所以小编记录下这个问题 微信小程序授权登录问题 思路 参考网上一大堆资料 核心关键字: 自定义授权+鉴权 (说的通俗就是解决办法就是改造springSecurity的过滤器) 参考文章 https://www.jb51.net/article/204704.htm 总的来说的 通过自定义的WxAppletAuthenticationFilter替换默认的UsernameP

  • SpringSecurity构建基于JWT的登录认证实现

    最近项目的登录验证部分,采用了 JWT 验证的方式.并且既然采用了 Spring Boot 框架,验证和权限管理这部分,就自然用了 Spring Security.这里记录一下具体实现. 在项目采用 JWT 方案前,有必要先了解它的特性和适用场景,毕竟软件工程里,没有银弹.只有合适的场景,没有万精油的方案. 一言以蔽之,JWT 可以携带非敏感信息,并具有不可篡改性.可以通过验证是否被篡改,以及读取信息内容,完成网络认证的三个问题:"你是谁"."你有哪些权限".&qu

  • SpringBoot集成SpringSecurity和JWT做登陆鉴权的实现

    废话 目前流行的前后端分离让Java程序员可以更加专注的做好后台业务逻辑的功能实现,提供如返回Json格式的数据接口就可以.SpringBoot的易用性和对其他框架的高度集成,用来快速开发一个小型应用是最佳的选择. 一套前后端分离的后台项目,刚开始就要面对的就是登陆和授权的问题.这里提供一套方案供大家参考. 主要看点: 登陆后获取token,根据token来请求资源 根据用户角色来确定对资源的访问权限 统一异常处理 返回标准的Json格式数据 正文 首先是pom文件: <dependencies

  • SpringSecurity整合jwt权限认证的全流程讲解

    JWT 本文代码截取自实际项目. jwt(Json Web Token),一个token,令牌. 简单流程: 用户登录成功后,后端返回一个token,也就是颁发给用户一个凭证.之后每一次访问,前端都需要携带这个token,后端通过token来解析出当前访问对象. 优点 1.一定程度上解放了后端,后端不需要再记录当前用户是谁,不需要再维护一个session,节省了开销. 2.session依赖于cookie,某些场合cookie是用不了的,比如用户浏览器cookie被禁用.移动端无法存储cooki

  • mall整合SpringSecurity及JWT实现认证授权实战

    目录 摘要 项目使用框架介绍 SpringSecurity JWT JWT的组成 JWT实例 JWT实现认证和授权的原理 Hutool 项目使用表说明 整合SpringSecurity及JWT 在pom.xml中添加项目依赖 添加JWT token的工具类 添加SpringSecurity的配置类 相关依赖及方法说明 添加RestfulAccessDeniedHandler 添加RestAuthenticationEntryPoint 添加AdminUserDetails 添加JwtAuthen

  • VUE项目中调用高德地图的全流程讲解

    目录 前言 申请高德key 技术选型 刷新页面,地图加载偶尔失败 在绑定插件的时候,控制台报错 a[d].split is not a function 原生调用高德API 总结 前言 相信大家或多或少都接触过在大屏的项目,在大屏项目中除了用到了echarts中的行政地图,街道地图也是很常见的,今天主要来说一下在vue中调用高德地图遇到的一些问题. 申请高德key 无论我们使用任何方式调用高德地图都需要在高德地图开放平台中申请key 注册帐号 访问高德地图开发平台根据实际情况填写就可以(实名认证

  • mall整合SpringSecurity及JWT认证授权实战下

    目录 摘要 登录注册功能实现 添加UmsAdminController类 添加UmsAdminService接口 添加UmsAdminServiceImpl类 修改Swagger的配置 给PmsBrandController接口中的方法添加访问权限 认证与授权流程演示 运行项目,访问API 未登录前访问接口 登录后访问接口 访问需要权限的接口 改用其他有权限的帐号登录 摘要 接上一篇,controller和service层的代码实现及登录授权流程演示. 登录注册功能实现 添加UmsAdminCo

  • JWT登录认证实战模拟过程全纪录

    目录 Token 认证流程 Token 认证优点 JWT 结构 JWT 基本使用 实战:使用 JWT 登录认证 附:为什么使用jwt而不使用session 总结 Token 认证流程 作为目前最流行的跨域认证解决方案,JWT(JSON Web Token) 深受开发者的喜爱,主要流程如下: 客户端发送账号和密码请求登录 服务端收到请求,验证账号密码是否通过 验证成功后,服务端会生成唯一的 token,并将其返回给客户端 客户端接受到 token,将其存储在 cookie 或者 localStro

  • SpringBoot整合SpringSecurity实现JWT认证的项目实践

    目录 前言 1.创建SpringBoot工程 2.导入SpringSecurity与JWT的相关依赖 3.定义SpringSecurity需要的基础处理类 4. 构建JWT token工具类 5.实现token验证的过滤器 6. SpringSecurity的关键配置 7. 编写Controller进行测试 前言 微服务架构,前后端分离目前已成为互联网项目开发的业界标准,其核心思想就是前端(APP.小程序.H5页面等)通过调用后端的API接口,提交及返回JSON数据进行交互.在前后端分离项目中,

  • 浅谈ASP.NET Core 中jwt授权认证的流程原理

    1,快速实现授权验证 什么是 JWT ?为什么要用 JWT ?JWT 的组成? 这些百度可以直接找到,这里不再赘述. 实际上,只需要知道 JWT 认证模式是使用一段 Token 作为认证依据的手段. 我们看一下 Postman 设置 Token 的位置. 那么,如何使用 C# 的 HttpClient 访问一个 JWT 认证的 WebAPI 呢? 下面来创建一个 ASP.NET Core 项目,尝试添加 JWT 验证功能. 1.1 添加 JWT 服务配置 在 Startup.cs 的 Confi

  • springboot+jwt实现token登陆权限认证的实现

    一 前言 此篇文章的内容也是学习不久,终于到周末有时间码一篇文章分享知识追寻者的粉丝们,学完本篇文章,读者将对token类的登陆认证流程有个全面的了解,可以动态搭建自己的登陆认证过程:对小项目而已是个轻量级的认证机制,符合开发需求: 二 jwt实现登陆认证流程 用户使用账号和面发出post请求 服务器接受到请求后使用私钥创建一个jwt,这边会生成token 服务器返回这个jwt给浏览器 浏览器需要将带有token的jwt放入请求头 每次手到客户端请求,服务器验证该jwt的token 验证成功返回

随机推荐