SpringSecurity+JWT实现前后端分离认证和授权 合集版

本文详细介绍了如何使用SpringSecurity和JWT进行登录验证,并通过配置类、过滤器和常量类实现登录流程和权限管理。通过两次HTTP请求,首次登录获取JWT,后续请求携带JWT进行资源访问。同时,文章提供了测试接口和登录失败处理的实现方式。
摘要由CSDN通过智能技术生成

目录

前言:

第一部分:代码测试:

分为两次请求

1.第一个请求就是类似于前端请求一个登录SpringSecurity内置验证的安全登录中心

​编辑2.第二个请求就是在第一个请求成功的基础上进行请求的

第二部分:流程分析【重点】

 第一个请求的流程:

具体步骤:

 第二个请求的流程:

第二个请求的前提:

具体步骤:

 对两次请求的总结:

 为什么要这样做?

 第三部分:编码

配置类:WebSecurityConfig类

常量类:JwtConstant 类

过滤器:JwtTokenCheckFilter类

启动类:SpringsecurityJwtApplication 类

测试时的接口:TestController类


前言:

所有代码看最后即可 。

第一部分:代码测试:

我们测试时,采用的是postman充当客户端进行测试。

先进行启动:

分为两次请求

1.第一个请求就是类似于前端请求一个登录SpringSecurity内置验证的安全登录中心

解析如下:

为的是获取到请求服务端中我们进行配置的Controller接口的权限。如果这个登录都验证不过去,那么就没有权限去进行请求之后的

Controller中配置的接口资源

我们对于SpringSecurity内置的安全登录,是可以进行自定义配置请求地址的:我们这里配置的是/doLogin。如图:在配置类中进行配置的

我们对于SpringSecurity内置的安全登录,是可以进行自定义配置可成功登录该内置登录中心的成员用户信息,我们这里配置的是:

用户名:mxy 密码:123以及所拥有的权限集合

2.第二个请求就是在第一个请求成功的基础上进行请求的

第二个请求就是对后端服务器中的Controller类中的接口对应的资源进行的请求。

当第一个请求执行成功之后,说明SpringSecurity登录验证已经成功 。

说明可以继续请求后端资源接口。

第一个请求之后,我们就会获取一个JWT。这个JWT中就包含着我们的用户信息。然后我们就把这个JWT配置给Headers中的Authorization。

第二部分:流程分析【重点】

 第一个请求的流程:

首先我们使用postman进行请求我们自定义设置的SpringSecurity安全登录验证的接口doLogin

并且配置请求url的携带参数。参数为登录的用户名以及密码 !

具体步骤:

 1.无论什么请求都会先进行经过过滤器

 2.但是由于这是security的登录验证请求,其中并没有token,所以我们直接通过过滤器中的解析JWT的过程。

3. 跳过过滤器之后,我们会进行验证security登录。此时我们要进行判断,postman发送的请求url

中携带的用户名以及密码参数是否在我们自定义的用户中

我们自定义的用户为 :用户名为mxy ,密码为123

 4.如果登录失败:会进入到我们登录失败的处理器中进行处理

 5.如果登录成功:同理会跳转到登录成功处理器进行处理操作:

登录成功处理器,最后会把用户信息进行设置给JWT中。

然后把JWT设置给access_token。并且设置过期时间,以及token的类型为bearer

 设置完成之后进行使用流进行写出 其中还使用到了转化。

 我们通过流写出响应给客户端浏览器的数据对应的格式为JSON,编码为Utf-8

 6.结束之后,postman(客户端)上会显示服务端响应回来的数据,该数据为Json格式

这些数据就是我们上边利用PrintWriter打印流进行写出的:

 第二个请求的流程:

首先第二个请求就是对后端服务器中的Controller类中的接口对应的资源进行的请求。

当第一个请求执行成功之后,说明SpringSecurity登录验证已经成功 说明可以继续请求后端资源接口。

第二个请求的前提:

(1)第一个请求成功

(2)携带第一个请求之后返回的access_token。这个access_token其实就是JWT。我们把这个JWT

设置给Authorization。格式为 :bear+空格+JWT

具体步骤:

 1.这个请求首先会被过滤器进行拦截

2.过滤器发现这个用户已经成功进行了security验证登录。那么当该用户进行这一次请求的时候,

肯定会携带一个access_token。这个东西就是JWT。JWT中封装的就是我们用户的信息 !

所以说:过滤器的作用就是继续解析这个JWT。

3.解析完成之后,再进行封装设置。

 对两次请求的总结:

当第一次请求时,客户端postman携带想要进行登录的用户信息,服务端接收请求,然后进行登录

验证。登录验证成功之后,也就是第一次请求之后,服务端想要写出响应给客户端浏览器用户信息

(即是JWT),但是服务端会把JWT封装叫做access_token进行真正响应给客户端。

所以其实JWT=access_token。

第一次请求之后,客户端具有了已经登录用户的信息 即是access_token。

当第二次请求发出时,客户端postman把第一次请求获取到access_token中JWT数据进行设置给

第二次请求的Header字段中Authorization。第二次请求进行发出,此时过滤器进行拦截到然后进行

对这个Authorization字段进行解析。解析得到的是用户信息,此时对于第二次请求到达服务端而言

我们已经具有了该用户的信息。我们可以通过该用户的信息进行检验是否该用户已经完成了

security登录验证。如果完成了,那么就可以接着进行下一步对后端服务器资源的请求。所以说,

当第二次请求完成了过滤器对其JWT的解析之后,后端服务器就已经获取到了这个用户对应的信

息。所以当我们进行分为后端服务器资源的时候,我们会进行权限判断,此时由于我们第二次请求

中的过滤之后,我们已经把解析之后的JWT的数据 即是用户信息的数据设置给了

所以说,我们就可以进行判断是否该服务端Controller的接口是否具有用户权限进行访问 

 为什么要这样做?

因为在微服务的项目中,我们可以是多个服务端。所以说,我们一个用户信息保证完成一次

security登录验证之后,我们就可以在所有服务端进行请求资源的权力。

 第三部分:编码

配置类:WebSecurityConfig类

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtTokenCheckFilter jwtTokenCheckFilter ;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //所有请求发送给服务端都会先执行过滤器中的操作
        http.addFilterBefore(jwtTokenCheckFilter, UsernamePasswordAuthenticationFilter.class) ;
        //去掉防护
        http.csrf().disable() ;
        //表单提交
        http.formLogin()
                .loginProcessingUrl("/doLogin")
                .successHandler(authenticationSuccessHandler()) //登录成功处理器
                .failureHandler(authenticationFailureHandler())//登录失败处理器
                .permitAll() ; //放行
        //禁用session 因为我们使用的是前后端分离。根本不需要session,只要你具有token(jwt)就可以进行访问
        http.sessionManagement().disable() ;
        //放行所有的接口 因为我们使用了jwt ;所以只要请求携带了jwt 只要能被解析,那么就相当于登录成功!
        http.authorizeRequests().antMatchers("/**").permitAll() ;//所以我们对所有请求进行放行 不做安全登录验证
    }
    //进行配置用户信息
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                //创建增加用户mxy
                .withUser("mxy")
                //设置密码 并且进行加密
                .password(passwordEncoder().encode("123"))
                //对用户进行设置访问权限
                .authorities("sys:add","sys:query") ;
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder() ;
    }
    /**
     * 登录成功时的返回值
     * 1.拿到用户信息
     * 2.根据用户信息生成jwt
     * 3.写出去给客户端浏览器
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request,
                                                HttpServletResponse response,
                                                Authentication authentication) throws IOException, ServletException {
                //设置编码以及数据格式类型为json
                response.setContentType("application/json;charaset=utf-8");
                //获取到用户信息 进行强转
                User principal = (User) authentication.getPrincipal();
                //拿到我们的用户名
                String username = principal.getUsername() ;
                //拿到我们的权限对应的集合
                Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                List<String> authStrs = new ArrayList<>(authorities.size() * 2);//使用2倍的大小 防止轻易扩容
                //遍历authStrs集合 进行设置权限
                authorities.forEach(new Consumer<GrantedAuthority>() {
                    @Override
                    public void accept(GrantedAuthority author) {
                        //该接口重写的实现方法 内置了功能
                        //这个方法的最终作用效果为:
                        // 遍历authorities集合之后 把集合中的每一个元素的权限都设置给authStrs 这个List集合中
                        authStrs.add(author.getAuthority());
                    }
                });
                //生成jwt jwt其实就是我们所说的token
                //1.颁发token的时间
                Date createTime = new Date() ;
                //2.token过期的时间
                Calendar now = Calendar.getInstance();
                now.set(Calendar.SECOND,7200);//设置过期时间
                Date expireTime = now.getTime();//获取到过期时间expireTime
                //3.token中的header
                HashMap<String, Object> header = new HashMap<>(4);
                header.put("alg","HS256") ;
                header.put("typ","JWT") ;
                //4.token的载体
                String jwt = com.auth0.jwt.JWT.create()
                        .withHeader(header)
                        .withIssuedAt(createTime)
                        .withExpiresAt (expireTime)
                        //自定义的进行设置一些信息到JWT中
                        .withClaim("username",username)//设置用户名
                        .withClaim("auths",authStrs) //设置用户对应的权限
                        //5.设置签名
                        .sign(Algorithm.HMAC256(JwtConstant.SIGN)) ;
                //生成JWT之后,我们就要将其写出去给客户端浏览器
                HashMap<String,Object> map = new HashMap<>(8) ;
                //jwt其实就是我们所说的token
                map.put("access_token",jwt) ;
                map.put("expire_time",JwtConstant.EXPIRE_TIME) ;
                map.put("type","bearer") ;//记住:token就一种类型 那就是bearer
                ObjectMapper objectMapper = new ObjectMapper();
                String s = objectMapper.writeValueAsString(map) ;
                PrintWriter writer = response.getWriter() ;
                writer.write(s) ;
                writer.flush();
                writer.close();
            }
        } ;
    }
    //登录失败时
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest request,
                                                HttpServletResponse response,
                                                AuthenticationException e) throws IOException, ServletException {
                //设置编码格式以及数据的类型为Json格式
                response.setContentType("application/json;charset=utf-8");
                //失败的话,那么就写出去给客户端浏览器即可
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code",401) ;
                map.put("msg","登录失败") ;
                ObjectMapper objectMapper = new ObjectMapper() ;
                //把Map集合类型的数据转化为String类型的数据
                String s = objectMapper.writeValueAsString(map) ;
                PrintWriter writer = response.getWriter() ;
                writer.write(s) ;
                writer.flush();
                writer.close();
            }
        } ;
    }
}

常量类:JwtConstant 类

public interface JwtConstant {

    /**
     * 过期时间
     */
    Integer EXPIRE_TIME = 7200 ;
    /**
     * 加密的字符串
     */
    String SIGN = "security-jwt-sign" ;
    /**
     * header头里面的信息
     */
    String AUTHORIZATION = "Authorization" ;
}

过滤器:JwtTokenCheckFilter类

@Configuration
public class JwtTokenCheckFilter extends OncePerRequestFilter {

    /**
     * 每次请求都会走这个方法
     * jwt会从Headers带回来
     * 解析jwt 然后把解析后的数据设置到上下文去
     * 每一个请求都会进行解析,会大大的增大登录服务器的负担。所以说:jwt的性能没有session好
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        //设置一个响应编码
        response.setContentType("application/json;charset=utf-8");
        //1.拿到url
        String path = request.getRequestURI() ;
        String method = request.getMethod() ;//拿到请求的类型 : Get Post Put Delete
        if ("/doLogin".equals(path) && "POST".equalsIgnoreCase(method)) {
            //直接进行放行,因为登录没有token
            filterChain.doFilter(request,response) ;
            return;
        }
        //验证jwt 先进行获取到 请求中携带的Headers字段中的authorization数据
        //该数据的格式为:bearer+空格+jwt
        String authorization = request.getHeader(JwtConstant.AUTHORIZATION) ;
        //如果authorization有文本 即是有内容的意思
        if (StringUtils.hasText(authorization)) {
            //把authorization中的无用前缀 bearer+空格给进行删除 剩余的就是jwt的内容
            String jwt = authorization.replaceAll("bearer ","") ;
            //解析jwt
            //第一步:先进行验证签名 验证完签名之后才可以进行解析
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(JwtConstant.SIGN)).build() ;
            //jwt解析之后的类型为 DecodedJWT
            DecodedJWT decodedJWT = null ;
            try {
                decodedJWT = jwtVerifier.verify(jwt) ;
            } catch (JWTVerificationException e) {
                //报错了,写一个401异常
                response.getWriter().write("token验证失败");
                return;
            }
            //拿到解析之后的jwt了 即是对应的用户信息的数据。我们给后端服务器进行设置一下这些数据
            //拿到解析后数据中的用户名username的数据:
            Claim usernameClaim = decodedJWT.getClaim("username") ;
            String username = usernameClaim.asString(); //再开一层化为String类型
            //拿到jwt中的权限数据
            Claim authsClaim = decodedJWT.getClaim("auths") ;
            List<String> authStrs = authsClaim.asList(String.class); //String.class 表示转化为的集合中的元素类型为String
            //转变权限信息  容量设置为2倍避免轻易就扩容
            ArrayList<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>(authStrs.size() * 2);
            authStrs.forEach(new Consumer<String>() {
                //该接口对象中重写方法的作用就是:
                //把authStrs集合中的每一个String类型的元素进行遍历
                // 然后转变为SimpleGrantedAuthority类型的元素设置到simpleGrantedAuthorities集合中
                @Override
                public void accept(String authStr) {
                    simpleGrantedAuthorities.add(new SimpleGrantedAuthority(authStr)) ;
                }
            });
            //变成Security认识的对象
            UsernamePasswordAuthenticationToken authenticationToken =
                    //该token参数为:用户名 密码和权限集合列表
                    new UsernamePasswordAuthenticationToken(username,null,simpleGrantedAuthorities) ;
            // 怎么才能让security认识呢?
            //void setAuthentication(Authentication var1);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            //代码继续往下执行 放行
            filterChain.doFilter(request,response) ;
        }
    }
}

启动类:SpringsecurityJwtApplication 类

@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringsecurityJwtApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringsecurityJwtApplication.class, args);
    }

}

测试时的接口:TestController类

@RestController
public class TestController {

    @GetMapping("test1")
    @PreAuthorize("hasAuthority('sys:add')")
    public String test1() {
        return "add成功" ;
    }
    @GetMapping("test2")
    @PreAuthorize("hasAuthority('sys:delete')")
    public String test2() {
        return "delete成功" ;
    }
}

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值