spring security jwt 简单使用

spring security jwt 简单使用

首先要定义一个实体类UserDetail.java;并集成org.springframework.security.core.userdetails.UserDetails类;


import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author javal
 * createAt: 2021/11/09
 */
@Data
public class UserDetail implements UserDetails {
    private Long id;

    private String username;

    private String password;

    private Collection<? extends GrantedAuthority> authorities;

    private String inrOrgNo;
    /** 片区名称 */
    private String orgVstdArea;
    /** 支行名称 */
    private String orgShrtNm;

    public UserDetail(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }

    public UserDetail() {
    }

    public UserDetail(String username) {
        this.username = username;
    }

    public UserDetail(Long id, String username, String password, Collection<? extends GrantedAuthority> authorities, String inrOrgNo, String orgVstdArea, String orgShrtNm) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.authorities = authorities;
        this.inrOrgNo = inrOrgNo;
        this.orgVstdArea = orgVstdArea;
        this.orgShrtNm = orgShrtNm;
    }

    private List<GrantedAuthority> mapToGrantedAuthorities(List<String> authorities) {
        return authorities.stream()
                .map(authority -> new SimpleGrantedAuthority(authority))
                .collect(Collectors.toList());
    }
    private List<GrantedAuthority> mapToGrantedAuthorities(String authoritie) {
        return Arrays.asList(new SimpleGrantedAuthority(authoritie));
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return null;
    }

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

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

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

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

    public String getInrOrgNo() {
        return inrOrgNo;
    }

    public void setInrOrgNo(String inrOrgNo) {
        this.inrOrgNo = inrOrgNo;
    }

    public String getOrgVstdArea() {
        return orgVstdArea;
    }

    public void setOrgVstdArea(String orgVstdArea) {
        this.orgVstdArea = orgVstdArea;
    }

    public String getOrgShrtNm() {
        return orgShrtNm;
    }

    public void setOrgShrtNm(String orgShrtNm) {
        this.orgShrtNm = orgShrtNm;
    }
}

定义接口org.springframework.security.core.userdetails.UserDetailsService实现类UserDetailsServiceImpl.java,众所周知,该接口只有一个方法loadUserByUsername,通过该方法使用用户名称查询用户信息。并返回org.springframework.security.core.userdetails.UserDetails.UserDetailsService对象。即该对象为返回的实际用户信息。


import com.gbicc.framework.entity.UserDetail;
import com.gbicc.system.entity.SysUser;
import com.gbicc.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    SysUserMapper sysUserMapper;

    @Override
    public UserDetail loadUserByUsername(String name) throws UsernameNotFoundException {
        SysUser user = sysUserMapper.loadUserByUsername(name);
        if (user == null){
            throw new UsernameNotFoundException(String.format("'%s'.这个用户不存在", name));
        }else {
            //todo 用户的角色和权限都是放置在 GrantedAuthority 中的;角色带ROLE_,权限没有(target:history:list)。
            List<GrantedAuthority> authorityList = new ArrayList<>();//设置用户权限
            if (user.getInrOrgNo().equals("0")){
                authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));//普通用户
            }else {
                authorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));//超级管理员
            }
            UserDetail detail = new UserDetail(user.getUserName(),user.getPassword(),authorityList);//正儿八经返回数据内容;
            return detail;
        }
    }
}

贴一下org.springframework.security.core.userdetails.UserDetails.UserDetailsService接口的源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

定义filter拦截器jwt的拦截器JwtAuthenticationTokenFilter.java集成类OncePerRequestFilter


import com.gbicc.framework.security.utils.JwtTokenUtil;
import com.gbicc.framework.security.config.UserDetailsServiceImpl;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * token校验
 * @author: javal
 * createAt: 2018/9/14
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.header}")
    private String token_header;

    @Resource
    private UserDetailsServiceImpl userDetailsService;

    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String requestHeader = request.getHeader(this.token_header);
        String username = null;
        String authToken = null;
        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            authToken = requestHeader.substring(7);
            try {
                username = jwtTokenUtil.getUsernameFromToken(authToken);
            } catch (ExpiredJwtException e) {
            }
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        chain.doFilter(request, response);
    }
}

定义认证失败无权限处理类JwtAuthenticationEntryPoint


import com.gbicc.framework.entity.ResultCode;
import com.gbicc.framework.entity.ResultJson;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;

/**
 * @author: javal
 * createAt: 2018/9/20
 */
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        //验证为未登陆状态会进入此方法,认证错误
        System.out.println("认证失败:" + authException.getMessage());
        response.setStatus(200);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter printWriter = response.getWriter();
        String body = ResultJson.failure(ResultCode.UNAUTHORIZED, authException.getMessage()).toString();
        printWriter.write(body);
        printWriter.flush();
    }
}

定义退出功能接口类LogoutSuccessHandlerImpl,(可以选择不定义)


import com.gbicc.common.utils.ServletUtils;
import com.gbicc.framework.entity.UserDetail;
import com.gbicc.monitor.mapper.SysLogininforMapper;
import com.gbicc.monitor.entity.SysLogininfor;
import com.gbicc.system.service.LoginService;
import eu.bitwalker.useragentutils.UserAgent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {

    @Value("${jwt.header}")
    private String tokenHeader;

    @Autowired
    LoginService loginService;

    @Autowired
    SysLogininforMapper logininforMapper;

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        UserDetail user = loginService.getUserByToken(httpServletRequest.getHeader(tokenHeader));
        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        // 获取客户端操作系统
        String os = userAgent.getOperatingSystem().getName();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();

        String ip = "unknown";
        if (httpServletRequest == null)
        {
            ip= "unknown";
        }
        ip = httpServletRequest.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = httpServletRequest.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = httpServletRequest.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = httpServletRequest.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = httpServletRequest.getHeader("X-Real-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = httpServletRequest.getRemoteAddr();
        }
        ip="0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
        SysLogininfor logininfor = new SysLogininfor();
        logininfor.setUserName(user.getUsername());
        logininfor.setIpaddr(ip);
        logininfor.setOs(os);
        logininfor.setBrowser(browser);
        logininfor.setMsg("退出成功");
        logininfor.setStatus("0");
        logininforMapper.insertSysLogininfor(logininfor);
        String token = httpServletRequest.getHeader(tokenHeader);
        loginService.logout(token);
    }
}

最后贴上seerurity的配置类(核心配置类)


import com.gbicc.framework.security.handle.JwtAuthenticationEntryPoint;
import com.gbicc.framework.security.handle.LogoutSuccessHandlerImpl;
import com.gbicc.framework.security.security.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @author javal
 * createAt: 2021/11/09
 */
@Configuration
@EnableWebSecurity //这个注解必须加,开启Security
@EnableGlobalMethodSecurity(prePostEnabled = true) //保证post之前的注解可以使用
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 退出处理类
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /***
     * 实现AuthenticationEntryPoint这个接口,发现没有凭证,往response中放些东西。
     */
    @Autowired
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    /**
     * 用户认证类
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /***
     * TOKEN认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 装载BCrypt密码编码器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份认证接口;先来这里认证一下
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    //拦截在这配
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 由于使用的是JWT,我们这里不需要csrf
                .csrf().disable()
                //这里面主要配置如果没有凭证,可以进行一些操作。
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                //todo /ststem模块只能被ADMIN角色或者USER角色中任意角色就访问
                .antMatchers("/ststem/**").hasAnyRole("ADMIN","USER")
                // 对于获取token的rest api要允许匿名访问
                .antMatchers("/login/**","/getInfo/**","/common/**").permitAll()
                .antMatchers("/api/v1/login", "/api/v1/sign", "/error/**").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        // 禁用缓存
        httpSecurity.headers().cacheControl();
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 解决 无法直接注入 AuthenticationManager
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}


application.yml配置文件

server:
  port: 8087

gbicc:
  profile: D:/iuoooo/uploadPath
#  profile: /usr/loacl/iuoooo/uploadPath

# PageHelper分页插件
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql


spring:
  application:
    name: digital
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/digitalize_bank?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5
      min-idle: 10                                          # 最小连接数
      max-active: 20                                        # 最大连接数
      max-wait: 60000                                       # 获取连接时的最大等待时间
      min-evictable-idle-time-millis: 300000                # 一个连接在池中最小生存的时间,单位是毫秒
      time-between-eviction-runs-millis: 60000              # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒
      filters: stat,wall                                    # 配置扩展插件:stat-监控统计,log4j-日志,wall-防火墙(防止SQL注入),去掉后,监控界面的sql无法统计
      validation-query: SELECT 1                            # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效
      test-on-borrow: true                                  # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能
      test-on-return: true                                  # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能
      test-while-idle: true                                 # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能
      stat-view-servlet:
        enabled: true                                       # 是否开启 StatViewServlet
        allow: 127.0.0.1                                    # 访问监控页面 白名单,默认127.0.0.1
        deny: 192.168.56.1                                  # 访问监控页面 黑名单
        login-username: admin                               # 访问监控页面 登陆账号
        login-password: admin                               # 访问监控页面 登陆密码
      filter:
        stat:
          enabled: true                                     # 是否开启 FilterStat,默认true
          log-slow-sql: true                                # 是否开启 慢SQL 记录,默认false
          slow-sql-millis: 5000                             # 慢 SQL 的标准,默认 3000,单位:毫秒
          merge-sql: false                                  # 合并多个连接池的监控数据,默认false


# 输出sql语句日志
logging:
  level:
    com.gbicc: debug

# MyBatis配置
mybatis:
  # 指定扫描包名不生效~~~
  #type-aliases-package: com.gbicc.framework.entity,com.gbicc.monitor.entity,com.gbicc.quarzt.entity,com.gbicc.system.entity
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath*:mybatis/**/*Mapper.xml

# JWT
jwt:
  header: Authorization
  secret: abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
  #token有效期一天
  expiration: 86400
  tokenHead: "Bearer "

jwt快捷工具类(这个建议集成redis),本工具类未集成redis。因此将用户信息存储至SecurityContextHolder.getContext().setAuthentication(authentication);
中,可在JwtAuthenticationTokenFilter类中看到。

package com.gbicc.framework.security.utils;

import com.gbicc.framework.entity.UserDetail;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

@Component
public class JwtTokenUtil implements Serializable {
    private static final long serialVersionUID = -3301605591108950415L;

    @Value("${jwt.secret}")
    private  String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    @Value("${jwt.token}")
    private String tokenHeader;

    private Clock clock = DefaultClock.INSTANCE;

    //token存储容器
    Map<String,String> tokenMap=new ConcurrentHashMap<>(32);

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails.getUsername());
    }

    private String doGenerateToken(Map<String, Object> claims, String subject) {
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }

    private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration);
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        UserDetail user = (UserDetail) userDetails;
        final String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername())
                && !isTokenExpired(token)
        );
    }

    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }


    public UserDetail getUserFromToken(String token){
        UserDetail userDetail;
        try {
            Claims claims=getAllClaimsFromToken(token);
            String username=claims.getSubject();
            if (!username.equals("")){
                Authentication authentication=SecurityContextHolder.getContext().getAuthentication();
                userDetail = (UserDetail) authentication.getPrincipal();
            }else {
                userDetail=new UserDetail();
            }
        }catch (Exception e){
            userDetail=null;
        }
        return userDetail;
    }

    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    public void putToken(String userName,String token){
        tokenMap.put(userName,token);
    }

    public void deleteToken(String userName){
        tokenMap.remove(userName);
    }


    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(clock.now());
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }
}

OK,结束,未能详细介绍;有时间补充一下。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值