从零玩转SpringSecurity+JWT整合前后端分离3

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 从零玩转SpringSecurity+JWT整合前后端分离

下期文章整合Jwt+Security

二、创建jwt_secueiry工程

image-20210408163126803-50bbd7e0a0ae4e57817fc2a1be61da22.png

image-20210409141835617-af369efe01d64a39b2f10b227aad67fa.png

image-20210409141844128-ba7d40ebaabe485a90856862c07c8e97.png

1 、创建工程完毕之后

2、修改启动类 新增注解

image-20210409143008707-538ab5e7c44d4739b2fa12ccd695c296.png

3、修改配置文件 yml

server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://yby6.com:3310/psringsecurity?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: xxxx
    password: xxxx
  redis:
    host: xxxx
    port: xxxx
    database: xxxx
    password: xxxx
mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4、使用idea连接数据库,进行代码生成

image-20210409143458753-a3752e7d01694a04ab206d06c7a538ca.png

可以看到除了persistent_logins 其它的就是RBAC表了, 如果不懂RBAC的小伙伴的话请自行百度学习即可 so easy to happy

image-20210409143910328-de2e7b5830a5425da8eaed20e709091b.png

右击生成User表CRUD

image-20210409144110298-9427f8ebf3fc4b1c98a5b37601f83d89.png

image-20210409144118147-a0ab2843ebb24b2bbcb6b89132e75ad4.png

image-20210409144217100-d2e0e5b424604d7a8268e93debc07c59.png

image-20210409150023499-efdf07d0c52d4ae8ba81cd4b8e3f6d03.png

5、创建完毕,开始配置SpringSecurity 的配置啦。

1. 创建WebSecurityConfig配置类

package top.yangbuyi.config;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.yangbuyi.constant.JwtConstant;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.*;
/**
 * ClassName: WebSecurityConfig
 *
 * @author yangshuai
 * @Date: 2021-04-09 14:42
 * @Description: http请求配置 $
 **/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 权限403返回json
     */
    @Autowired
    private AccessDecision accessDecision;
    /**
     * redis
     */
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    /**
     * http请求
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭csrf攻击
        http.csrf().disable();
        // 登录配置
        http.formLogin()
                .loginProcessingUrl("/doLogin") // 指定登录地址
                .successHandler(authenticationSuccessHandler()) // 登录成功执行的
                .failureHandler(authenticationFailureHandler()) // 登录失败执行的
                .permitAll();
        // 403 权限不足
        http.exceptionHandling().accessDeniedHandler(accessDecision);
        // 基于token方式 不会存储session来进行登录判断了
        http.sessionManagement().disable();
        // 集成JWT 全部放行接口  使用jwt过滤器来进行 鉴权
        http.authorizeRequests().antMatchers("/**").permitAll();
    }
    /**
     * 登录成功的 handle 
     *
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (request,response,authentication) -> {
         System.out.println("登录成功了");
        // 把json串写出去
        response.setContentType("application/json;charset=utf-8");
        HashMap<String, Object> map = new HashMap<>(8);
        map.put("code", 200);
        map.put("msg", "登录成功");
        // 把用户信息返回给前端 让前端可以保存起来
        map.put("data", authentication);
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(map);
        // 写出去
        PrintWriter writer = response.getWriter();
        writer.write(s);
        // 刷新流 关闭流
        writer.flush();
        writer.close();
        };
    }
    /**
     * 登录失败的json
     *
     * @return
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {
            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();
            String s = objectMapper.writeValueAsString(map);
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }
}

新增jwt依赖

<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.11.0</version>
        </dependency>

6.自定义配置用户信息(数据库当中获取)

1.修改user实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class sysUser implements Serializable, UserDetails {
    private Integer userid;
    private String username;
    private String userpwd;
    private String sex;
    private String address;
    private List<String> authors = Collections.emptyList();
    private static final long serialVersionUID = 1L;
    /**
     * 返回查询出来的权限给到springSecurity
     *
     * @return the authorities, sorted by natural key (never <code>null</code>)
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorityArrayList = new ArrayList<>();
        if (this.authors.size() > 0) {
            authors.forEach(e -> authorityArrayList.add(new SimpleGrantedAuthority(e)));
        }
        return authorityArrayList;
    }
    /**
     * Returns the password used to authenticate the user.
     *
     * @return the password
     */
    @Override
    public String getPassword() {
        return this.userpwd;
    }
    /**
     * Indicates whether the user's account has expired. An expired account cannot be
     * authenticated.
     *
     * @return <code>true</code> if the user's account is valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    /**
     * Indicates whether the user is locked or unlocked. A locked user cannot be
     * authenticated.
     *
     * @return <code>true</code> if the user is not locked, <code>false</code> otherwise
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    /**
     * Indicates whether the user's credentials (password) has expired. Expired
     * credentials prevent authentication.
     *
     * @return <code>true</code> if the user's credentials are valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    /**
     * Indicates whether the user is enabled or disabled. A disabled user cannot be
     * authenticated.
     *
     * @return <code>true</code> if the user is enabled, <code>false</code> otherwise
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

2.创建UserDetailsServiceImpl 到config配置下

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import top.yangbuyi.domain.sysUser;
import top.yangbuyi.mapper.sysUserMapper;
import java.util.List;
/**
 * ClassName: UserDetailsServiceImpl
 *
 * @author yangshuai
 * @Date: 2021-04-09 14:55
 * @Description: 用户信息配置 $
 **/
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private sysUserMapper sysUserMapper;
    /**
     * 登录时会来到这里进行 用户校验 相当于shiro当中的 认证用户真实性
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名称查询用户信息
        sysUser sysUser = sysUserMapper.selectName(username);
        if (sysUser != null){
            List<String> a = sysUserMapper.selectAuthor(sysUser.getUserid());
            System.out.println("权限标识符:{}"+ a);
            sysUser.setAuthors(a);
        }
        return sysUser;
    }
}

3.修改mapper

@Select(" select * from sys_user where username = #{username}  ")
    sysUser selectName(String username);
    @Select(" select distinct  sp.percode from sys_user_role sur left join sys_role_permission srp on sur.roleid = srp.roleid left join sys_permission sp on srp.perid = sp.perid where sur.userid = #{userid} ")
    List<String> selectAuthor(Integer userid);

4.修改WebSecurityConfig配置文件

image-20210409151016613-edf3977f2e6f4efcb3cc9f7b1eff0aef.png

4.启动项目测试登录

zhangsan/123456

相当于复习了上面的知识点只是将内存用户改为自己数据库当中的用户

芜湖!!!报错了,咱们看到控制台打印 咦熟悉~~~~

加密器没有注入欸,在SpringSeccurity当中是需要加密器来进行登录的

来设置设置。。。

image-20210409154214246-5a60a7931da4470781d0667552919883.png

修改web配置文件

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

image-20210409154722633-f9eeb594d16645a48179bf23a2d2313b.png

恭喜恭喜 登录成功了

image-20210409154815893-4783ad8b65c44b3a865c78af387c9e57.png

image-20210409155029456-061fc31b5abe425c9cdaebc6897997fe.png

7.咦不对呀 不是要玩jwt嘛 ,不急 这就开始…

思考:

1. jwt用来干啥啊????

哎,搞这么久还不知道? 用来搞权限啊 如果用户没有进行登录 直接访问我们的业务 那肯定不行的呀 那么带着这些问题 我们冲冲冲!!!

2. 登录时我们如何接入jwt呢?

在SpringSecurity登录成功时会执行successHandel里面的自定义方法,我们在里面把用户信息存储到JWT然后呢

这时我们就需要使用到过滤器了,在springsecurity执行登录前 我们自己来进行校验登录 即可

看我操作…

1. 登录成功后我们进行将登录成功的用户数据存储到JWT 修改登录成功的handel 用于jwt颁发token

/**
     * 登录成功的 handle
     * 1. 拿到用户的信息
     * 2. 组装JWT
     * 3. 写出去
     *
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (request,response,authentication) -> {
            // 1.拿去用户数据
            UserDetails principal = (UserDetails) authentication.getPrincipal();
            String username = principal.getUsername();
            // 2.拿起权限
            Collection<? extends GrantedAuthority> authorities = principal.getAuthorities();
            List<String> authorizations = new ArrayList<>(authorities.size() * 2);
            authorities.forEach(e -> authorizations.add(e.getAuthority()));
            // 3.组装jwt
            // 生成jwt
            // 颁发时间
            Date createTime = new Date();
            // 过期时间
            Calendar now = Calendar.getInstance();
            // 设置未来的时间
            now.set(Calendar.MINUTE, JwtConstant.EXPIRETIME);
            Date expireTime = now.getTime();
            // header
            HashMap<String, Object> header = new HashMap<>(4);
            header.put("alg", "HS256");
            header.put("typ", "JWT");
            // 设置载体
            String sign = JWT.create()
                    .withHeader(header)
                    .withIssuedAt(createTime)
                    .withExpiresAt(expireTime)
                    .withClaim("username", username)
                    .withClaim("authorizations", authorizations)
                    .sign(Algorithm.HMAC256(JwtConstant.SIGN));
            ObjectMapper objectMapper = new ObjectMapper();
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("access_token", sign);
            map.put("expire_time", JwtConstant.EXPIRETIME);
            map.put("type", "bearer");
            // 保存redis  --- 客户端和服务器来建立有状态 
            redisTemplate.opsForValue().set(JwtConstant.JWT_KET + sign, sign, Duration.ofMinutes(JwtConstant.EXPIRETIME));
            String s = objectMapper.writeValueAsString(map);
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }

2.创建JwtCheckToken类 用户校验用户来的token是否合法并且是否由我颁发的

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import top.yangbuyi.constant.JwtConstant;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
 * ClassName: JwtCheckFiter
 *
 * @author yangshuai
 * @Date: 2021-03-29 09:56
 * @Description: jwt鉴权 $
 **/
@Component
public class JwtCheckFilter extends OncePerRequestFilter {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 每一次请求都会进行执行改过滤器
     * 每次请求都会走这个方法
     * jwt 从header带过来
     * 解析jwt
     * 设置到上下文当中去
     * jwt 性能没有session好
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        String requestURI = httpServletRequest.getRequestURI(); // 获取请求 url 来判断是否是登录
        String method = httpServletRequest.getMethod(); // 请求类型
        if ("/doLogin".equals(requestURI) && "POST".equalsIgnoreCase(method)) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        // 判断是否带入了token
        String header = httpServletRequest.getHeader(JwtConstant.AUTHORIZATION);
        if (StringUtils.hasText(header)) {
            // 解析真正的 token
            header = header.replaceAll(JwtConstant.JWTTYPE, "");
            // 判断token是否存在
            Boolean aBoolean = stringRedisTemplate.hasKey(JwtConstant.JWT_KET + header);
            // 校验是否过期 登出
            if (!aBoolean) {
                // 说明已经登出或者过期
                httpServletResponse.getWriter().write("token过期,请重新登录!");
                return;
            }
            // 解析验证
            JWTVerifier build = JWT.require(Algorithm.HMAC256(JwtConstant.SIGN)).build();
            DecodedJWT verify = null;
            try {
                verify = build.verify(header);
            } catch (JWTVerificationException e) {
                httpServletResponse.getWriter().write("token验证失败");
                return;
            }
            // 拿到解析后的jwt了 后端服务器没保存用户信息 给他设置进去
            Claim usernameClaim = verify.getClaim("username");
            String username = usernameClaim.asString();
            // 拿到权限
            Claim authsClaim = verify.getClaim("authorizations");
            List<String> authStrs = authsClaim.asList(String.class);
            // 转变权限信息
            ArrayList<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>(authStrs.size() * 2);
            authStrs.forEach(auth -> simpleGrantedAuthorities.add(new SimpleGrantedAuthority(auth)));
            // 变成security认识的对象` 
            // 参数一: 用户名称
            // 参数二:密码
            // 参数三:权限标识
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, simpleGrantedAuthorities);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken); // 重新存储到Security当中
            filterChain.doFilter(httpServletRequest, httpServletResponse); // 放行
            return;
        }
        httpServletResponse.getWriter().write("token验证失败.");
        return;
    }
}

3.修改web配置文件

将自定义的过滤器放在springSecurity执行登录之前调用,用于校验token 的合法权限

image-20210409160647950-b2136ecdee80499ea82b6d5ef2da06ce.png

4.重新启动测试token

登录成功,返回token

image-20210409160845615-5a455b91013444978fc77cd7619796fc.png

根据token带入 调用业务接口

image-20210409161014525-dc70b01ea0be42e696a974211f0e971c.png

好像忘记写业务接口了 不过没关系 补上不就行了

image-20210409161103689-bd07f0ab0e0b4fefa9d472ec32b5732f.png

5.创建IndexController

/**
 * ClassName: IndexController
 *
 * @author yangshuai
 * @Date: 2021-04-09 16:11
 * @Description: 业务 $
 **/
@RestController
public class IndexController {
    @RequestMapping("query")
    @PreAuthorize("hasAuthority('sys:query')")
    public String query() {
        return "query";
    }
    @RequestMapping("add")
    @PreAuthorize("hasAuthority('sys:add')")
    public String add() {
        return "add";
    }
    @RequestMapping("delete")
    @PreAuthorize("hasAuthority('sys:delete')")
    public String delete() {
        return "delete";
    }
    @RequestMapping("update")
    @PreAuthorize("hasAuthority('sys:update')")
    public String update() {
        return "update";
    }
}

重新启动测试token

可以带入刚刚我们的登录过的token 因为我们设置了过期时间 2小时嘛

image-20210409161416705-eef6428224714d38a8fc58ba273725b8.png

欸 访问成功 我们试一试 没有权限的

image-20210409161459254-337b81a29ac8471893fabaefd243c6c6.png

8.JWT登出

思考:

1.咋记录登录的唯一性呀

思路:

在用户登录成功后进入到handel我们进行记录当前jwt颁发的token到redis当中

然后就是,访问登出接口 我们根据传递来的token前往redis当中查询,判断是否存在 存在删除 即可。

这就引发出了我们自定义的 过滤器来校验 上次登录的token判断redis当中是否存在 就判断为 过期或者登出.

这段代码功能我已经写在Demo里面的IndexController了 小伙伴们课自己研究查看。

@Autowired
    private StringRedisTemplate stringRedisTemplate;
    @PostMapping("toLogout")
    public String toLogout() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String header = request.getHeader(JwtConstant.AUTHORIZATION);
        String jwtToken = header.replaceAll(JwtConstant.JWTTYPE, "");
        if (StringUtils.hasText(jwtToken)) {
            // 移除token
            stringRedisTemplate.delete(JwtConstant.JWT_KET + jwtToken);
        }
        return "退出成功!";
    }

到此 从零玩转 jwt+SpirngSeccurity 就结束了哦!

我们下次再见…

个人博客网站: https://www.yby6.com/

本文Demo: https://gitee.com/yangbuyi/bky_yby

🍺你的压力源于无法自律,只是假装努力,现状跟不上你内心的欲望,所以你焦急又恐慌—杨不易.|

相关文章
|
3天前
|
存储 JSON JavaScript
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)-1
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)
73 0
|
3天前
|
安全 数据安全/隐私保护
Springboot+Spring security +jwt认证+动态授权
Springboot+Spring security +jwt认证+动态授权
|
3天前
|
安全 Java 数据库
SpringSecurity+JWT前后端分离架构登录认证
在SpringSecurity实现前后端分离登录token认证详解_springsecurity前后端分离登录认证-CSDN博客基础上进行重构,实现前后端分离架构登录认证,基本思想相同,借鉴开源Gitee代码进行改造,具有更好的代码规范。
186 1
|
3天前
|
JSON 前端开发 安全
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)-2
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)
61 0
|
3天前
|
安全 Java API
深度解析 Spring Security:身份验证、授权、OAuth2 和 JWT 身份验证的完整指南
Spring Security 是一个用于保护基于 Java 的应用程序的框架。它是一个功能强大且高度可定制的身份验证和访问控制框架,可以轻松地集成到各种应用程序中,包括 Web 应用程序和 RESTful Web 服务。 Spring Security 提供了全面的安全解决方案,用于身份验证和授权,并且可以用于在 Web 和方法级别上保护应用程序。
325 0
|
3天前
|
JSON 安全 Java
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token5
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token5
|
3天前
|
NoSQL Java Redis
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token4
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token4
|
3天前
|
Java
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token4
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token4
|
3天前
|
Java
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token3
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token3
|
3天前
|
存储 JSON 算法
SpringBoot之JWT令牌校验
SpringBoot之JWT令牌校验
23 2