SpringBoot(8) ------>集成SpringSecurity与Jwt

一、简介

1、基于Token的鉴权机制

 基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或会话信息。这也就意味着基于tokent认证机制的应用不需要去考虑用户在哪一台服务器登陆了,这就为应用的扩展提供了便利

     流程是这样的

  • 用户使用用户名密码请求服务器
  • 服务器进行验证用户信息(用户名、权限、部门、角色)
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附加这个token
  • 服务器验证token,并返回数据

      这个token必须要在每次请求时发送给服务器,它应该保存在请求头中,另外,服务器要支持CORS(跨来源资源共享)策略,一般在服务端设置"Access-Control-Allow-Origin* " 就可以了

2、SpringSecurity

  Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求.

Spring Security的控制力度很细,但是是依赖于spring

3、Jwt的定义及组成

  json web token是为了在网络应用环境中传递声明而执行的一种基于json的开放标准。特别适合分布式站点的单点登录场景。jwt的声明一般被用来在身份提供者和服务提供者之间传递被认证的用户身份信息,以便于从服务器获取资源,也可以增加一些额外的其他业务逻辑所用到的生命信息。

  JWTJSON Web Token)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名

1)头部(Header

  JWT需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象:

{

  "typ": "JWT",

  "alg": "HS256"

}

  在这里,说明了这是一个JWT,并且所用的签名算法是HS256算法。

对它也要进行Base64编码,之后的字符串就成了JWTHeader(头部):eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

2)载荷(Payload)

  我们先将用户认证的操作描述成一个JSON对象。其中添加了一些其他的信息,帮助今后收到这个JWT的服务器理解这个JWT

{

    "sub": "1",

    "iss": "http://localhost:8000/auth/login",

    "iat": 1451888119,

    "exp": 1454516119,

    "nbf": 1451888119,

    "jti": "37c107e4609ddbcc9c096ea5ee76c667"

}

这里面的前6个字段都是由JWT的标准所定义的。

  • sub: JWT所面向的用户
  • iss: JWT的签发者
  • iat(issued at): 在什么时候签发的token
  • exp(expires): token什么时候过期
  • nbf(not before)token在此时间之前不能被接收处理
  • jtiJWT IDweb token提供唯一标识

这些定义都可以在JWT 标准 中找到。

  将上面的JSON对象进行base64编码可以得到下面的字符串:

eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWx
ob3N0OjgwMDFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0NTQ1MTYxMTksIm5iZiI6MTQ1MTg4OD
ExOSwianRpIjoiMzdjMTA3ZTQ2MDlkZGJjYzljMDk2ZWE1ZWU3NmM2NjcifQ

  这个字符串我们将它称作JWTPayload(载荷)

如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串:

var base64url = require('base64url')var header = {

    "from_user": "B",

    "target_user": "A"

}

console.log(base64url(JSON.stringify(header)))

注:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

3)签名(Signature)

  将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9j

YWxob3N0OjgwMDFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0N

TQ1MTYxMTksIm5iZiI6MTQ1MTg4ODExOSwianRpIjoiMzdjMTA3ZTQ2MDlkZGJjYzljMD

k2ZWE1ZWU3NmM2NjcifQ

最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret:

HMACSHA256(

    base64UrlEncode(header) + "." +

    base64UrlEncode(payload),

    secret

)

这样就可以得到我们加密后的内容:

 

wyoQ95RjAyQ2FF3aj8EvCSaUmeP0KUqcCJDENNfnaT4

 

这一部分又叫做签名

最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwMDFcL2F

1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0NTQ1MTYxMTksIm5iZiI6MTQ1MTg4ODExOSwianRp

IjoiMzdjMTA3ZTQ2MDlkZGJjYzljMDk2ZWE1ZWU3NmM2NjcifQ.wyoQ95RjAyQ2FF3aj8EvCSaUmeP0KUqcCJDENNf

 

二、Springboot中集成SpringSecurity与JWT

1、向pom文件添加依赖

    <!--security依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--JWT(Json Web Token)登录支持-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
View Code

2、application.yml添加配置

#jwt配置
jwt:
  #定义我们的盐  密码
  secret: mySecret
  #过期时间(单位s)
  expiration: 1800
  #token 的类型 说明他以 bearer 开头
  tokenHead: bearer
  #token 对应的 key
  tokenHeader: Authorization
#  {Authorization: "bearer sdfdsfsdfsdfdsfsdfadfdsf"}
View Code

3、config配置

  1)FilterConfig(跨域配置)

/**
 * @author liangd
 * date 2020-12-10 16:00
 * code 跨域配置
 * .@Order 注解用来声明组件的顺序,值越小,优先级越高,越先被执行/初始化。如果没有该注解,则优先级最低。
 */
@Configuration
@Order(1)
public class FilterConfig {
    private CorsConfiguration buildConfig(){
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 允许任何的head头部
        corsConfiguration.addAllowedHeader("*");
        // 允许任何域名使用
        corsConfiguration.addAllowedOrigin("*");
        // 允许任何的请求方法
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        return corsConfiguration;
    }

    /**
     * 添加CorsFilter拦截器,对任意的请求使用
     * @return CorsFilter
     */
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }
    /*
        配置security-jwt步骤总结:
        1、引入pom依赖
        2、配置yml
        3、新建vo登录参数          LoginParams
        4、创建jwt工具类          JwtTokenUtil
        5、配置Jwt登录授权过滤器   JwtAuthenticationTokenFilter
        6、实现登录生成token逻辑   SysUserController

        7、跨域配置               FilterConfig
        8、配置当未登录或者token失效访问接口时,自定义的返回结果 RestAuthenticationEntryPoint
        9、配置访问接口没有权限时,自定义的返回结果             RestfulAccessDeniedHandler

     */
}
View Code

  2)SecurityConfig(全局配置)

/**
 * @author liangd
 * date 2020-12-10 14:43
 * <p>
 * .@EnableGlobalMethodSecurity 是否允许方法上设置权限
 * prePostEnabled 方法执行前/后 默认为false
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

    /**
     * 跨域配置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
                .csrf().disable()
                // 基于token,所以不需要 securityContext
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //放行的资源路径
                .antMatchers("/css/**", "/js/**", "/fonts/**", "/user/login", "/user/register").permitAll()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                //任何请求都需要认证
                .anyRequest().authenticated()
                .and()
                .userDetailsService(userDetailsService)
        ;
        //自定义 登录界面
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        //添加自定义未授权和未登录结果返回
        http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint);
    }

   /*@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //设置哪些路径可以访问
                .antMatchers("/css/**", "/js/**", "/fonts/**").permitAll()
                //需要相应的角色才能访问
                .antMatchers("/users/**").hasRole("ADMIN")
                .anyRequest().authenticated()   // 任何请求都需要认证
                .and()
                .formLogin() //基于Form表单登录验证
                .and()
                .userDetailsService(userDetailsService);
    }*/

    /**
     * 创建passwordEncoder对象
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
View Code
保护URL常用的方法有:
authenticated()  保护URL,需要用户登录
permitAll()  指定URL无需保护,一般应用与静态资源文件
hasRole(String role)  限制单个角色访问,角色将被增加 “ROLE_” .所以”ADMIN” 将和 “ROLE_ADMIN”进行比较.
hasAuthority(String authority)  限制单个权限访问
hasAnyRole(String… roles)  允许多个角色访问.
hasAnyAuthority(String… authorities)  允许多个权限访问.
access(String attribute)  该方法使用 SpEL表达式, 所以可以创建复杂的限制.
hasIpAddress(String ipaddressExpression)  限制IP地址或子网

4、filter

  JwtAuthenticationTokenFilter(JWT登录授权过滤器)
package com.donleo.security.jwt.filter;

import com.donleo.security.jwt.utils.JwtTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
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;

/**
 * @author liangd
 * date 2020-12-10 15:39
 * code JWT登录授权过滤器
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    /**
     * Authorization
     */
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    /**
     * bearer
     */
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        //从 header  中获取 Authorization
        String authHeader = request.getHeader(this.tokenHeader);
        // 判断 authHeader  不为空  并且以 bearer 开头
        if (authHeader != null) {
            boolean b1 = StringUtils.startsWithIgnoreCase(authHeader,this.tokenHead);
            if (b1) {
                //截取 bearer 后面的字符串  并且 两端去空格(获取token)// The part after "Bearer "
                String authToken = authHeader.substring(this.tokenHead.length()).trim();

                String username = jwtTokenUtil.getUserNameFromToken(authToken);
                LOGGER.info("checking username:{}", username);
                // 用户名不为空  并且SecurityContextHolder.getContext()  存储 权限的容器中没有相关权限则继续
                boolean b = SecurityContextHolder.getContext().getAuthentication() == null;
                if (username != null && b) {
                    //从数据库读取用户信息
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                    //校验token
                    if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                        //UsernamePasswordAuthenticationToken(var1,var2,var3)
                        //第二个参数相当于密码校验
                        //第三个参数为用户拥有的权限校验
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        WebAuthenticationDetails details = new WebAuthenticationDetailsSource().buildDetails(request);
                        //设置用户ip
                        authentication.setDetails(details);
                        LOGGER.info("authenticated user:{}", username);
                        //存入本线程的安全容器   在访问接口拿到返回值后 要去主动清除 权限,避免干扰其他的线程
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
        }
        chain.doFilter(request, response);
    }
    /*
        Jwt登录拦截过滤器
        1、拦截用户请求、获取请求头
        2、判断请求头是否为空并且是否是以bearer开头
        3、截取bearer后面的字符串并去掉两端空格获取token
        4、从token中获取用户名
        5、判断用户名不为空,并且SecurityContextHolder.getContext() 存储权限的容器中没有相关权限则继续
        6、根据用户名查询数据库
        7、校验token,判断用户是否登录、是否有权限以及token是否过期
        8、校验该用户拥有的权限
        9、存入用户ip (0:0:0:0:0:0:0:1)
        10、存入本线程的安全容器  在访问接口拿到返回值后 要去主动清除 权限,避免干扰其他的线程
     */
}
View Code

5、utils工具类

  1)JwtTokenUtil(生成token以及解析token工具类)

/**
 * @author liangd
 * date 2020-12-10 15:10
 * code JwtToken生成的工具类
 * <p>
 * JWT token的格式:header.payload.signature
 * header的格式(算法、token的类型):
 * {"alg": "HS512","typ": "JWT"}
 * payload的格式(用户名、创建时间、生成时间):
 * {"sub":"wang","created":1489079981393,"exp":1489684781}
 * signature的生成算法:
 * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
 */
@Component
public class JwtTokenUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    /**
     * 盐
     */
    @Value("${jwt.secret}")
    private String secret;
    /**
     * 过期时间
     */
    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 根据 负载(用户名 部门 权限 等) 生成JWT的token
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 从token中获取JWT中的负载
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            LOGGER.info("JWT格式验证失败:{}", token);
        }
        return claims;
    }

    /**
     * 生成token的过期时间   *1000,秒换成毫秒
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 从token中获取登录用户名
     */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 验证token是否还有效
     *
     * @param token       客户端传入的token
     * @param userDetails 从数据库中查询出来的用户信息
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 判断token是否已经失效
     */
    private boolean isTokenExpired(String token) {
        Date expiredDate = getExpiredDateFromToken(token);
        return expiredDate.before(new Date());
    }

    /**
     * 从token中获取过期时间
     */
    private Date getExpiredDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * 根据用户信息生成token
     * jwt中的token由三部分组成 ----头部、载荷和签名
     *  1、头部(bearer)       ------放在最前
     *  2、载荷(用户认证信息)   ------放在中间(以"."拼接)
     *  3、签名(密钥secret)    ------放在最后
     *
     * 这里根据用户名和时间生成token
     *
     * eyJhbGciOiJIUzUxMiJ9
     *   .eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2MDc1OTIyODYxOTAsImV4cCI6MTYwNzU5NDE1Nn0
     *   .apZ0vpePj-yHpM9K444GbeB-R3EMmpx3XPBchb--IB6EDB1g_036fHfohunK1g-M2Grli8Ncg0FT6Oksk_Jx_g
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /**
     * 判断token是否可以被刷新
     */
    public boolean canRefresh(String token) {
        return !isTokenExpired(token);
    }

    /**
     * 刷新token
     */
    public String refreshToken(String token) {
        Claims claims = getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }
}
View Code

  2)RestAuthenticationEntryPoint(未登录返回结果)

package com.donleo.security.jwt.utils;

import cn.hutool.json.JSONUtil;
import com.donleo.security.jwt.common.CommonResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

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

/**
 * @author liangd
 * date 2020-12-10 15:50
 * code 当未登录或者token失效访问接口时,自定义的返回结果
 */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage())));
        response.getWriter().flush();
    }
}
View Code

  3)RestfulAccessDeniedHandler(没有权限返回结果)

package com.donleo.security.jwt.utils;

import cn.hutool.json.JSONUtil;
import com.donleo.security.jwt.common.CommonResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

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

/**
 * @author liangd
 * date 2020-12-10 15:56
 * code 当访问接口没有权限时,自定义的返回结果
 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));
        response.getWriter().flush();
    }
}
View Code

6、model层

  1)SysUser(实现UserDetails)
package com.donleo.security.jwt.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Date;
import java.util.Set;

/**
 * @author liangd
 * date 2020-12-08 18:23
 * code 用户表
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "sys_user")
public class SysUser implements UserDetails {
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private String username;
    private String password;
    private String icon;
    private String email;
    private String nickName;
    private String note;
    private Date createTime;
    private Date loginTime;
    private Integer status;
    /**
     * .@JsonIgnoreProperties防止存redis反序列化时报错
     */
    @JsonIgnoreProperties(ignoreUnknown = true)
    private Set<? extends GrantedAuthority> authorities;

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

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

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

    public void setAccountNonExpired(boolean accountNonExpired) {

    }

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

    public void setAccountNonLocked(boolean accountNonLocked) {

    }

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

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {

    }

    @Override
    public boolean isEnabled() {
        if(this.status==null){
            return false;
        }
        return this.status==1;
    }

    public void setEnabled(boolean enabled) {

    }

}
View Code
  2)SysPermission(实现GrantedAuthority)
package com.donleo.security.jwt.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;

import java.util.Date;

/**
 * @author liangd
 * date 2020-12-08 18:28
 * code  权限表
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "sys_permission")
public class SysPermission implements GrantedAuthority {

    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private Integer pid;
    private String name;
    /**
     * 权限值
     */
    private String value;
    private String icon;
    private Integer type;
    private String uri;
    private Integer status;
    private Date createTime;
    private String sort;

    @Override
    public String getAuthority() {
        return this.value;
    }
}
View Code

7、service层

  ISysUserService(用户接口定义)
package com.donleo.security.jwt.service;

import com.donleo.security.jwt.model.SysPermission;
import com.donleo.security.jwt.model.SysUser;
import com.donleo.security.jwt.vo.LoginParams;

import java.util.List;

/**
 * @author liangd
 * date 2020-12-10 14:46
 * code
 */
public interface ISysUserService {
    /**
     * 根据用户名查询用户
     * @param name 用户名
     * @return SysUser
     */
    SysUser getUserByName(String name);

    /**
     * 根据用户Id查询用户权限
     * @param id 用户Id
     * @return List
     */
    List<SysPermission> getPermissionsByUserId(Integer id);

    /**
     * 用户登录
     * @param loginParams LoginParams
     * @return String
     */
    String login(LoginParams loginParams);

    /**
     * 用户注册
     * @param sysUser SysUser
     * @return Boolean
     */
    Boolean register(SysUser sysUser);
}
View Code

8、serviceImpl层

  1)SysUserServiceImpl(用户接口实现)
package com.donleo.security.jwt.service.impl;

import com.donleo.security.jwt.mapper.ISysUserMapper;
import com.donleo.security.jwt.model.SysPermission;
import com.donleo.security.jwt.model.SysUser;
import com.donleo.security.jwt.service.ISysUserService;
import com.donleo.security.jwt.utils.JwtTokenUtil;
import com.donleo.security.jwt.vo.LoginParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.Date;
import java.util.List;

/**
 * @author liangd
 * date 2020-12-10 14:47
 * code 用户逻辑实现层
 */
@Service
public class SysUserServiceImpl implements ISysUserService {
    @Autowired
    private ISysUserMapper sysUserMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    public SysUser getUserByName(String name) {
        List<SysUser> users = sysUserMapper.getUserByName(name);
        Assert.isTrue(users.size() == 1, "您输入的账户不存在,或者有多个相同的账户");
        return users.get(0);
    }

    @Override
    public List<SysPermission> getPermissionsByUserId(Integer id) {
        return sysUserMapper.getPermissionsByUserId(id);
    }

    @Override
    public String login(LoginParams loginParams) {
        String username = loginParams.getUsername();
        Assert.notNull(username, "账号必须不能为空");
        String password = loginParams.getPassword();
        Assert.notNull(password, "密码必须不能为空");
        SysUser userByName = getUserByName(username);
        //判断用户输入的密码与数据库中查出来的密码是否相等
        boolean matches = passwordEncoder.matches(password, userByName.getPassword());
        //如果密码相等,表明用户信息输入正确,生成一个token令牌,否则返回null
        if (matches) {
            return jwtTokenUtil.generateToken(userByName);
        }
        return null;
        /*
            生成token逻辑:
            根据用户的信息(例如用户名)、token的过期时间和密匙等通过base64加密生成token,
            最后拼接上头部信息,返回jwt token字符串
         */
    }

    @Override
    public Boolean register(SysUser sysUser) {
        String username = sysUser.getUsername();
        Assert.notNull(username, "用户名不能为空");
        String password = sysUser.getPassword();
        Assert.notNull(password, "密码不能为空");
        List<SysUser> user = sysUserMapper.getUserByName(username);
        if (user.size() >= 1) {
            return false;
        }
        sysUser.setPassword(passwordEncoder.encode(password));
        sysUser.setCreateTime(new Date());
        sysUserMapper.insert(sysUser);
        return true;
    }
}
View Code
  2)CustomUserDetailsService(自定义UserDetailsService)
package com.donleo.security.jwt.service.impl;

import com.donleo.security.jwt.model.SysPermission;
import com.donleo.security.jwt.model.SysUser;
import com.donleo.security.jwt.service.ISysUserService;
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.Service;

import java.util.HashSet;
import java.util.List;

/**
 * @author liangd
 * date 2020-12-08 19:10
 * code 自定义UserDetailsService
 */
@Service("userDetailsService")
public class CustomUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    ISysUserService sysUserService;

    /**
     * 从数据库读取用户名认证
     *
     * @param username 用户名
     * @return UserDetails
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserService.getUserByName(username);
        List<SysPermission> permissionList = sysUserService.getPermissionsByUserId(user.getId());
        //获取用户拥有的权限
        HashSet<SysPermission> permissions = new HashSet<>(permissionList);
        user.setAuthorities(permissions);
        return user;
    }
}
View Code

9、持久层

  ISysUserMapper(dao层接口定义)
/**
 * @author liangd
 * date 2020-12-10 14:37
 * code
 */
public interface ISysUserMapper extends BaseMapper<SysUser> {

    List<SysUser> getUserByName(String name);

    List<SysPermission> getPermissionsByUserId(Integer id);
}
View Code

10、mapper

  sql语句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.donleo.security.jwt.mapper.ISysUserMapper">

    <!--根据用户名查询用户-->
    <select id="getUserByName" parameterType="java.lang.String" resultType="com.donleo.security.jwt.model.SysUser">
        select * from  sys_user where  username =#{name}
    </select>

    <!--根据用户Id查询用户权限-->
    <select id="getPermissionsByUserId" parameterType="java.lang.Integer" resultType="com.donleo.security.jwt.model.SysPermission">
        select * from  sys_permission p where p.id in(
          select rp.permission_id from sys_role_permission_relation rp where rp.role_id  in
            (select ur.role_id from sys_user_role_relation ur WHERE ur.user_id =#{userId})
        UNION
            SELECT up.permission_id  from sys_user_permission_relation up WHERE up.type=1 and up.user_id=#{userId}
        )
        and p.id not in(
            SELECT up.permission_id  from sys_user_permission_relation up WHERE up.type=-1 and up.user_id=#{userId}
        )
    </select>

    <!--查询所有-->
    <select id="findAll" resultType="com.donleo.security.jwt.model.SysUser">
        select * from sys_user
    </select>

    <!--查询单个-->
    <select id="findById" resultType="SysUser">
        select * from sys_user where id = #{id,jdbcType=INTEGER}
    </select>
</mapper>
View Code

11、控制层(测试)

 1)SysUserController(登陆注册)

  登录成功生成token

package com.donleo.security.jwt.controller;

import com.donleo.security.jwt.common.CommonResult;
import com.donleo.security.jwt.model.SysUser;
import com.donleo.security.jwt.service.ISysUserService;
import com.donleo.security.jwt.vo.LoginParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

/**
 * @author liangd
 * date 2020-12-10 15:32
 * code 用户controller
 */
@RestController
@RequestMapping("/user")
public class SysUserController {

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

    @Autowired
    private ISysUserService userService;

    /**
     * 用户登录
     * @param loginParams
     * @return
     */
    @PostMapping("/login")
    public CommonResult login(@RequestBody LoginParams loginParams){
        HashMap<String, String> data = new HashMap<>();
        String token = null;
        try {
            token = userService.login(loginParams);
        } catch (Exception e) {
            e.printStackTrace();
            return CommonResult.validateFailed("用户名或密码错误");
        }
        if (StringUtils.isEmpty(token)){
            return CommonResult.validateFailed("用户名或密码错误");
        }
        data.put("tokenHead",tokenHead);
        data.put("access_token",token);
        // localStorage.setItem("Authorization","Bearer sdsdfdfds")
        // $ajax{data:{},type:"",header:{"Authorization":"Bearer sdsdfdfds"}}
        return CommonResult.success(data);
    }

    /**
     * 用户注册
     * @param sysUser
     * @return
     */
    @PostMapping("/register")
    public CommonResult register(@RequestBody SysUser sysUser){
        boolean b = userService.register(sysUser);
        if (b){
            return CommonResult.success("注册成功");
        }
        return CommonResult.failed("账号已存在");
    }
}
View Code
 2)   SysRoleController(方法上添加权限验证)

  添加@PreAuthorize 在执行此方法之前验证权限

package com.donleo.security.jwt.controller;

import com.donleo.security.jwt.common.CommonResult;
import com.donleo.security.jwt.mapper.ISysRoleMapper;
import com.donleo.security.jwt.model.SysRole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author liangd
 * date 2020-12-10 15:00
 * code 系统角色控制层
 */
@RestController
@RequestMapping("/role")
public class SysRoleController {

    @Autowired
    private ISysRoleMapper sysRoleMapper;

    @PreAuthorize("hasAuthority('wx:product:readtest')")
    @GetMapping("/findById")
    public CommonResult findById(Integer id){
        SysRole sysRole = sysRoleMapper.selectById(id);
        return CommonResult.success(sysRole);
    }

    @PreAuthorize("hasAuthority('wx:product:read')")
    @GetMapping("/findAll")
    public CommonResult findAll(){
        List<SysRole> sysRoleList = sysRoleMapper.selectList(null);
        return CommonResult.success(sysRoleList);
    }
}
View Code

源码地址:https://gitee.com/donleo/springboot-security-jwt

作者:donleo123
本文如对您有帮助,还请多推荐下此文,如有错误欢迎指正,相互学习,共同进步。
原文地址:https://www.cnblogs.com/donleo123/p/14291650.html