【spring-security基础】JWT前后端分离方案

【spring-security基础】JWT前后端分离方案,第1张

【spring-security基础】JWT前后端分离方案

目录

依赖引入功能清单及代码详解

整体配置自定义部分

1. 自定义认证2. 自定义鉴权3. 自定义异常处理4. 免鉴权资源忽略5. 登出状态清理 附

JWT token处理代码参考
实现代码以【spring-security基础】基于数据库的认证方式 为前提

依赖引入

jwt需要引入对应工具包,用于维护token


   io.jsonwebtoken
    jjwt
    0.9.1

功能清单及代码详解

    自定义认证
    自定义鉴权
    自定义异常处理
    免鉴权资源忽略
    登出状态清理
整体配置

这部分配置了自定义异常处理及过滤器

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 未授权访问
                .exceptionHandling()
                    // 认证失败
                    .authenticationEntryPoint(myUnAuthEntryPoint)
                    // 鉴权失败
                    .defaultAccessDeniedHandlerFor(new MyAccessDeniedHandler(),new AntPathRequestMatcher("
    @Override
    public UserDetails loadUserByUsername(String usename) throws UsernameNotFoundException {
        // 查询数据库
        QueryWrapper queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username",usename);
        UserInfo userInfo = userInfoDao.selectOne(queryWrapper);
        if (userInfo ==null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        // 获取用户角色
        List roles = rolePermissionDao.getRuleByUserId(userInfo.getId());
        // 获取用户权限(url)
        List per = rolePermissionDao.getPermissByUserId(userInfo.getId());
        for (String role : roles) {
            per.add("ROLE_"+role);
        }
        List auths = new ArrayList<>();
        for (String s : per) {
            GrantedAuthority e = new SimpleGrantedAuthority(s);
            auths.add(e);
        }
        // 返回认证主体UserDetails
        return new MyUser(userInfo.getUsername(),userInfo.getPassword(),auths);
        //使用非自定义的密码,可能需要进行加密后再放置进认证主体密码属性中去
        // return new User(userInfo.getUsername(),bCryptPasswordEncoder.encode(userInfo.getPassword()),auths);
    }
}

自定义密码处理器用于密码加密(如存储到数据库前的加密)及匹配鉴权

package cn.com.demo.config;

import cn.com.demo.util.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;


@Component
public class MyPasswordEncoder implements PasswordEncoder {
    
    @Override
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }

    
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equalsIgnoreCase(MD5.encrypt(rawPassword.toString()));
    }
}

2. 自定义鉴权

自定义鉴权主要收集用户的权限,提交security进行鉴权。
jwt实现时,通过访问request的header部分查询token,并进行有效期检查及获取其权限,这部分自定义可以实现jwt token有效期的检查,同时也可以完成手动退出时token仍在有效期可继续使用的问题。

package cn.com.demo.filter;

import cn.com.demo.config.MyTokenConfig;
import cn.com.demo.util.LocalCache;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

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.List;

public class MyTokenAuthFilter extends BasicAuthenticationFilter {
    MyTokenConfig myTokenConfig;
    public MyTokenAuthFilter(AuthenticationManager authenticationManager, MyTokenConfig myTokenConfig) {
        super(authenticationManager);
        this.myTokenConfig = myTokenConfig;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 获取认证成功的用户授权信息
        String token = request.getHeader(myTokenConfig.getTokenConfigProperties().getHeadName());
        if(!StringUtils.isEmpty(token)) {
            String userName = myTokenConfig.getUserName(token);
            List authorities = (List) LocalCache.get(userName);
            if(authorities != null) {
                // 放置权限到security上下文中
                UsernamePasswordAuthenticationToken auths = new UsernamePasswordAuthenticationToken(userName, token, authorities);
                SecurityContextHolder.getContext().setAuthentication(auths);
            }
        }
        // 过滤链需要继续向下
        chain.doFilter(request,response);
    }

    @Override
    protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException {
        System.out.println("鉴权通过。。。。");
    }

    @Override
    protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
        System.out.println("鉴权失败");
    }
}

需要注意的是,虽然覆写了onSuccessfulAuthentication、onUnsuccessfulAuthentication但并没有在执行链(doFilterdoFilterInternal)中完整处理,如需处理可参考其父类进行处理(因为这两个方法为BasicAuthenticationFilter 增强的两个方法,而非标准处理链上的方法)。

3. 自定义异常处理

这部分处理包括两部分;认证过程中异常的处理及权限不足时的处理,处理这部分主要用于在用户没有权限时覆盖默认的page转发及HTTP状态码

    通过认证入口点(AuthenticationEntryPoint)的实现,处理认证过程中的异常处理
public class MyUnAuthEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ResponseUtil.out(response, baseRepVo.UNAUTH);
    }
}
    通过访问限制(AccessDeniedHandler)接口的实现,可根据url配置不通的处理器
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        ResponseUtil.out(response, baseRepVo.FORBIDDEN);
    }
}
4. 免鉴权资源忽略

通过重写configure(WebSecurity webSecurity) 方法,配置其ignoring().antMatchers(String… antPatterns)方法 配置忽略过滤的资源,需要注意的是这里配置的资源将不会完整执行security的过滤器链,比如如果将认证过滤器(自定义)的url配置到这里,你的自定义认证过滤器也就不会生效了。
具体代码见整体配置。

5. 登出状态清理

通过实现LogoutHandler 接口完成退出登录的清理工作,需要注意的是,security有自己的会话维护(实现了SecurityContextHolderStrategy接口)。

public class MyLogoutHandler implements LogoutHandler {
    @Autowired
    private MyTokenConfig myTokenConfig;
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        String token = request.getHeader(myTokenConfig.getTokenConfigProperties().getHeadName());
        if(!StringUtils.isEmpty(token)) {
            // token清理工作,如在redis中删除token,防止未过期的token继续使用
            String userName = myTokenConfig.getUserName(token);
            LocalCache.remove(userName);
        }
        ResponseUtil.ok(response);
    }
}
附 JWT token处理

token的处理主要包括基于用户名(用户信息)生成token,及对token的解析(这里仅在token中放置了用户名信息)

package cn.com.demo.config;

import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.compression.GzipCompressionCodec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MyTokenConfig {
    @Autowired
    TokenConfigProperties tokenConfigProperties;
    public String getToken(String userName) {
        return Jwts.builder()
                .setSubject(userName)
                .setExpiration(new Date(System.currentTimeMillis() + tokenConfigProperties.getExpireTime()*1000))
                .signWith(SignatureAlgorithm.HS512,tokenConfigProperties.getSignKey())
                .compressWith(new GzipCompressionCodec()).compact();
    }
    public String getUserName(String token) {
        try {
            return Jwts.parser().setSigningKey(tokenConfigProperties.getSignKey()).parseClaimsJws(token).getBody().getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("token 解析异常");
        }
        return null;
    }
    public TokenConfigProperties getTokenConfigProperties(){
        return tokenConfigProperties;
    }
}

代码参考

demo代码地址 demo

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/zaji/5721682.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-12-18
下一篇2022-12-18

发表评论

登录后才能评论

评论列表(0条)

    保存