这里JWT相关的工具类代码就不多展示了。这边可以在网上找到很多的JWTUtils模板。

静态文件展示:

对于前端我们写一个用于测试的几个页面。

1、登录页面:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>登录</title>
        <script src="./script/jquery-3.7.1.min.js"></script>
    </head>
    <body>
        <table>
            <tr>
                <td><input id="act" type="text"></td>
            </tr>
            <tr>
                <td><input id="psd" type="password"></td>
            </tr>
            <td><button id="login">登录</button><button>注册</button></td>
        </table>

    </body>
    <script>
    var act = document.getElementById("act");
    var psw = document.getElementById("psd");
        $("#login").click(function(){
            var act = $('#act').val();
            var psw = $('#psd').val();
            $.ajax('login',{
                method: 'POST',
                contentType: 'application/json',
                dataType: 'json',
                data: JSON.stringify({uid: act,password: psw}),
                success:function (result){
                    alert(JSON.stringify(result))
                    console.log(result)
                },
                error:function (result){
                    alert(result.data)
                }
            })
        });
</script>
</html>

2、主页:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>管理页面</title>
</head>
<body>
    <table>
        <tr>
            <td>测试成功</td>
            <td>测试成功</td>
            <td>测试成功</td>
        </tr>
        <tr>
            <td>测试成功</td>
            <td>测试成功</td>
            <td>测试成功</td>
        </tr>
        <tr>
            <td>测试成功</td>
            <td>测试成功</td>
            <td>测试成功</td>
        </tr>
    </table>
</body>
</html>

文件目录展示:

配置SpringSecurity:

1. UserDetails

1.1、得到UserDetails方式(实现UserDetailsService接口的方式):

import com.cbq.springsecuritystudy6.entity.User;
import com.cbq.springsecuritystudy6.service.RoleService;
import com.cbq.springsecuritystudy6.service.UserService;
import com.cbq.springsecuritystudy6.vo.SecurityUser;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @description: 定制化用户登录数据获取方式
 * @author 长白崎
 * @date 2023/9/19 11:28
 * @version 1.0
 */
@Service
public class SecurityUserDetailServiceImpl implements UserDetailsService {
    @Resource
    UserService userService;

    @Resource
    private RoleService roleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userFromUid = userService.getUserFromUid(username);
        if(userFromUid==null){
            throw new UsernameNotFoundException("该用户不存在");
        }

        List<String> rolePermission = roleService.queryPermissionsByUserUid(username);

        SecurityUser securityUser = new SecurityUser(userFromUid);

        ArrayList<SimpleGrantedAuthority> authorityArrayList = new ArrayList<>();
        rolePermission.stream().forEach((v)-> authorityArrayList.add(new SimpleGrantedAuthority(v)));
        securityUser.setAuthorityList(authorityArrayList);

        return securityUser;
    }
}

1.2、得到UserDetails方式(实现UserDetails接口的方式)(推荐):


import com.cbq.springsecuritystudy6.entity.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

public class SecurityUser implements UserDetails {

    private final User user;
    private List<SimpleGrantedAuthority> authorityList;

    /**
     * 利用构造方法来注入User
     * @param user
     */
    public SecurityUser(User user) {
        this.user = user;
    }

    /**
     * 获取权限列表
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorityList;
    }

    /**
     * 设置权限列表
     * @param authorityList
     */
    public void setAuthorityList(List<SimpleGrantedAuthority> authorityList) {
        this.authorityList = authorityList;
    }

    public User getUser() {
        return user;
    }

    @Override
    public String getPassword() {
        String pws = user.getPassword();
        user.setPassword(null);
        return pws;
    }

    @Override
    public String getUsername() {
        return user.getUid();
    }

    /**
     * 账号是否未过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 账号是否未锁定
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 账号是否未过期
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 账号是否可用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

2、通过拦截器接口实现JWT的拦截认证:

这里就用Map来模拟Redis了。

import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.cbq.springsecuritystudy6.utils.JWTUtils;
import com.cbq.springsecuritystudy6.vo.SecurityUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

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.HashMap;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Component
public class JwtCheckFilter extends OncePerRequestFilter {
    //模拟Redis
    public static Map<String,Object> redis = new HashMap<>();
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        //获取token
        String authorization = request.getHeader("Authorization");
        //如果token不存在可能是其他白名单的请求,这里直接放行
        if(!StringUtils.hasText(authorization)){
            //放行
            filterChain.doFilter(request,response);
            return;
        }

        DecodedJWT token = JWTUtils.getToken(authorization);
        //获取uid
        Claim uid = token.getClaim("uid");

        System.out.println(uid.asString());

        //判断是否为非法token
        if(!JWTUtils.verify(authorization) && JWTUtils.isExpired(authorization)){
            try {
                throw new RuntimeException("非法Token");
            }catch (Exception e){
            }
        }

        String redisKey = "login:"+uid.asString();
        //从redis里面获取对象
        SecurityUser securityUser =(SecurityUser) redis.get(redisKey);
        //如果redis里面没有相应的token,那么说明token非法
        if(Objects.isNull(securityUser)){
            throw new RuntimeException("您未登录");
        }

        //TODO 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(securityUser,null,securityUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        //放行
        filterChain.doFilter(request,response);
    }
}

3、实现WebSecurityConfigurerAdapter接口对Security进行配置: