SpringBoot+SpringSecurity +Jwt 实现前后端分离认证 从0到实现

1.什么是JWT (JSON Web Token)

具体可查看这篇文章,把JWT总结的很到位 什么是jwt?

2.SpringSecurity 整合 Jwt

2.1导入项目所用到的依赖
 <!--mybatis-plus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <!-- JWT依赖 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <!-- security框架 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- mysql依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
2.2 添加配置文件 application.yml
server:
  port: 8010
spring:
  application:
    name: springboot-security-jwt
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=utf8&useSSL=false&zeroDateTimeBehavior=convertToNull
    password: root
    username: root

## 设置jwt的信息  (这个先设置上 后面我们会讲 这里配置的有什么作用)
jwt:
  # jwt要加密的 秘钥 
  secret: zheshiyigexiangmyuzhongdemiyaonizhidaolealdahwhdr
  # 设置jwt的header
  header: token
  # 不拦截的路径
  path: /login

2.3创建和数据库对应的entity表
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.util.Date;

/**
 * 2 * @Author: ZhangShuai
 * 3 * @Date: 2020/6/12 17:03
 * 4
 */
@Data
@TableName(value = "sys_auth_user")
public class SysAuthUser{
    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 用户名称
     */
    @TableField(value = "user_name")
    private String userName;

    /**
     * 用户密码
     */
    @TableField(value = "password")
    private String password;

    /**
     * 手机号
     */
    @TableField(value = "mobile_phone")
    private String mobilePhone;

    /**
     * 用户状态1.启用2.禁用
     */
    @TableField(value = "state")
    private Byte state;

    /**
     * 微信openid
     */
    @TableField(value = "open_id")
    private String openId;

    /**
     * 过期时间
     */
    @TableField(value = "expire_time")
    private Date expireTime;

}
2.4创建和数据库对应的dao层
import com.aaa.zhang.entity.SysAuthUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;

/**
 * 2 * @Author: ZhangShuai
 * 3 * @Date: 2020/6/12 17:03
 * 4
 */
@Repository
public interface SysAuthUserMapper extends BaseMapper<SysAuthUser> {

}
2.5 创建service层
import com.aaa.zhang.entity.SysAuthUser;


/**
 * 2 * @Author: ZhangShuai
 * 3 * @Date: 2020/6/12 17:03
 * 4
 */
public interface AuthUserService {


    /**
     * 根据用户名去查询 当前用户是否存在
     *
     * @param name
     * @return
     */
    SysAuthUser findByUserName(String name);

}

对应的service实现层

import com.aaa.zhang.dao.SysAuthUserMapper;
import com.aaa.zhang.entity.SysAuthUser;
import com.aaa.zhang.service.AuthUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 2 * @Author: ZhangShuai
 * 3 * @Date: 2020/6/12 17:03
 * 4
 */
@Service
public class AuthUserServiceImpl implements AuthUserService {

    @Autowired
    SysAuthUserMapper authUserMapper;


    //根据用户名来查询这个用户
    @Override
    public SysAuthUser findByUserName(String name) {
        LambdaQueryWrapper<SysAuthUser> lambda = new QueryWrapper<SysAuthUser>().lambda();
        lambda.eq(SysAuthUser::getUserName, name);
        return authUserMapper.selectOne(lambda);
    }

}
2.6 创建JwtUser 我们自定义 返回给Security 用户的内容
import lombok.Data;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Set;

/**
 * @author zhangshuai
 */
@Data
public class JwtUser implements UserDetails, CredentialsContainer {

    /**
     * 用户id
     */
    private Long id;

    /**
     * 用户账号
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 权限
     */
    private Set<GrantedAuthority> authorities;

    /**
     * 账号
     */
    private boolean accountNonExpired;

    /**
     * 账号是否被锁定
     */
    private boolean accountNonLocked;

    /**
     *
     */
    private boolean credentialsNonExpired;

    /**
     * 用户是否可用
     */
    private boolean enabled;

    public JwtUser() {
        this.accountNonExpired = true;
        this.accountNonLocked = true;
        this.credentialsNonExpired = true;
        this.enabled = true;
    }



    @Override
    public boolean isAccountNonExpired() {
        return this.accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return this.credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }

    @Override
    public void eraseCredentials() {
        this.password = null;
    }
}
2.7 创建UserDetailsServiceImpl 实现Security 的 自定义用户认证与授权
import com.aaa.zhang.entity.SysAuthUser;
import com.aaa.zhang.util.JwtUser;
import org.springframework.security.core.GrantedAuthority;
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.HashSet;
import java.util.Set;

/**
 * 2 * @Author: ZhangShuai
 * 3 * @Date: 2020/6/12 17:03
 * 4 自定义用户认证与授权
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    AuthUserService authUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysAuthUser authUser = authUserService.findByUserName(username);
        // 查出用户权限
        Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
        // 构建返回信息
        JwtUser jwtUser = new JwtUser();
        jwtUser.setUsername(authUser.getUserName());
        jwtUser.setPassword(authUser.getPassword());
        jwtUser.setAuthorities(grantedAuthorities);
        jwtUser.setId(authUser.getId());
        return jwtUser;
    }
}
2.8 创建JWT认证入口点 用来配置到 Security中
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

/**
 * 定义未认证信息
 */
@Component
public class JwtAuthentication implements AuthenticationEntryPoint, Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 定义未认证访问接口 的错误信息
     * @param request
     * @param response
     * @param authException
     * @throws IOException
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {

        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}
2.9 添加JwtUtils 类 用来加密信息 生成token
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cglib.core.internal.Function;
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;

@Component
public class JwtUtils implements Serializable {

    private static final long serialVersionUID = -2550185165626007488L;

    //设置令牌的过期时间
    public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;

    // jwt加密的秘钥
    @Value("${jwt.secret}")
    private String secret;

    /**
     * 从令牌中得到用户的信息
     * @param token
     * @return
     */
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    /**
     * 获取token 中的过期时间
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    /**
     * 得到这个token中的信息
     * @param token
     * @param claimsResolver
     * @param 
     * @return
     */
    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    /**
     * 使用秘钥去解开这个令牌
     * @param token
     * @return
     */
    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }

    /**
     * 检查令牌是否过期
     * @param token
     * @return
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * 拿到用户的信息生成token
     * @param userDetails
     * @return
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails.getUsername());
    }

    /**
     * 生成token
     * @param claims
     * @param subject
     * @return
     */
    private String doGenerateToken(Map<String, Object> claims, String subject) {
        return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                //设置token 5小时候过期
                .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
                .signWith(SignatureAlgorithm.HS256, secret).compact();
    }

    /**
     * 验证token 是否过期
     * @param token
     * @param userDetails
     * @return
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}
2.10 创建 JwtFilter 用来过滤每个请求 是否带有token 然后去判断这个token是否 合法
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
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.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import io.jsonwebtoken.ExpiredJwtException;

/**
 * jwt filter用来过滤 输入的token 是否有效
 */
@Component
public class JwtFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtUtils jwtUtils;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        //设置header信息 从token后面来取 生成的token
        final String requestTokenHeader = request.getHeader("token");
        String username = null;
        String jwtToken = null;
        if (requestTokenHeader != null ) {
            jwtToken = requestTokenHeader;
            try {
                username = jwtUtils.getUsernameFromToken(jwtToken);
            } catch (IllegalArgumentException e) {
                //抛出异常 告诉当前令牌无效了
                System.out.println("Unable to get JWT Token");
            } catch (ExpiredJwtException e) {
                //抛出错误码 前端根据错误码 跳转到登录页面 去重新刷新令牌
                System.out.println("JWT Token has expired");
            }
        }
        //如果当前令牌有效 并且 获取不到 Security中的认证信息
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            //我们拿着用户名 去请求一次 Security 然后我们手动设置到 Security中
            if (jwtUtils.validateToken(jwtToken, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // 去验证当前 密码和账号是否正确
                // 如果认证通过了 那么就
                // 设置到Security中
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}
2.11 创建WebSecurityConfig配置类 用于配置Security的认证流程等
import com.aaa.zhang.config.JwtAuthentication;
import com.aaa.zhang.config.JwtFilter;
import com.aaa.zhang.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.builders.WebSecurity;
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.authentication.UsernamePasswordAuthenticationFilter;

/**
 * 2 * @Author: ZhangShuai
 * 3 * @Date: 2020/6/12 17:03
 * 4
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 定义认证入口点
     */
    @Autowired
    private JwtAuthentication jwtAuthentication;

    /**
     * jwt 拦截器 通过这个拦截器 去判断这个token 是否可以使用
     */
    @Autowired
    private JwtFilter jwtFilter;

    /**
     * 登录的路径
     */
    @Value("${jwt.path}")
    private String loginPath;

    /**
     * 使用Security的认证管理器  认证操作security 给我们来做
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    /**
     * 使用security的默认密码加密方式
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 主要是配置这个Bean,用于授权服务器配置中注入
     * @return
     */
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService());
    }

    /**
     * 配置security不拦截的请求  下面可以根据需求自定义 用,号隔开
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 将 check_token 暴露出去,否则资源服务器访问时报 403 错误
        //配置暴露出去的端点  不用登陆就可以访问
        web.ignoring()
        		// 注意这里 我们把配置文件中的 login暴露出来 用来验证登录
                .antMatchers(
                        HttpMethod.POST,
                        loginPath
                )
                .antMatchers( "/GetToken", "/index.html", "/css/**", "/js/**",
                "/images/**",
                "/openid/login",
                "/oauth/check_token", "/queryToken");

    }

    /**
     * 设置security 对登录的一些操作
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //禁止跨域请求
        http.csrf().disable()
                .exceptionHandling().authenticationEntryPoint(jwtAuthentication)
                //关闭session
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //设置授权请求
                .authorizeRequests().antMatchers(loginPath).permitAll()
                //设置这个路径不用拦截
                .anyRequest()
                .authenticated()
                .and()
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        // 禁用页面缓存
        http.headers()
                .frameOptions().sameOrigin()
                .cacheControl();
    }
}
2.12 创建LoginDTO 用来接收前端发送给我们的参数
import lombok.Data;

@Data
public class LoginDTO {

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

}

2.13 创建Result 用来把我们的内容返回给前端
import lombok.Data;

/**
 * 统一返回值
 *
 * @author fengxinglie
 *
 */
@Data
public class Result {

	// 成功状态码
	public static final int SUCCESS_CODE = 200;

	// 请求失败状态码
	public static final int FAIL_CODE = 500;

	// 查无资源状态码
	public static final int NOTF_FOUNT_CODE = 404;

	// 无权访问状态码
	public static final int ACCESS_DINE_CODE = 403;

	/**
	 * 状态码
	 */
	private int code;

	/**
	 * 提示信息
	 */
	private String msg;

	/**
	 * 数据信息
	 */
	private Object data;

	/**
	 * 请求成功
	 *
	 * @return
	 */
	public static Result ok() {
		Result r = new Result();
		r.setCode(SUCCESS_CODE);
		r.setMsg("请求成功!");
		r.setData(null);
		return r;
	}

	/**
	 * 请求失败
	 *
	 * @return
	 */
	public static Result fail() {
		Result r = new Result();
		r.setCode(FAIL_CODE);
		r.setMsg("请求失败!");
		r.setData(null);
		return r;
	}

	/**
	 * 请求成功,自定义信息
	 *
	 * @param msg
	 * @return
	 */
	public static Result ok(String msg) {
		Result r = new Result();
		r.setCode(SUCCESS_CODE);
		r.setMsg(msg);
		r.setData(null);
		return r;
	}

	/**
	 * 请求失败,自定义信息
	 *
	 * @param msg
	 * @return
	 */
	public static Result fail(String msg) {
		Result r = new Result();
		r.setCode(FAIL_CODE);
		r.setMsg(msg);
		r.setData(null);
		return r;
	}

	/**
	 * 请求成功,自定义信息,自定义数据
	 *
	 * @param msg
	 * @return
	 */
	public static Result ok(String msg, Object data) {
		Result r = new Result();
		r.setCode(SUCCESS_CODE);
		r.setMsg(msg);
		r.setData(data);
		return r;
	}

	/**
	 * 请求失败,自定义信息,自定义数据
	 *
	 * @param msg
	 * @return
	 */
	public static Result fail(String msg, Object data) {
		Result r = new Result();
		r.setCode(FAIL_CODE);
		r.setMsg(msg);
		r.setData(data);
		return r;
	}
	public Result code(Integer code){
		this.setCode(code);
		return this;
	}


	public Result data(Object data){
		this.setData(data);
		return this;
	}

	public Result msg(String msg){
		this.setMsg(msg);
		return this;
	}

}
2.14 创建LoginController控制层
import com.aaa.zhang.config.JwtUtils;
import com.aaa.zhang.dto.LoginDTO;
import com.aaa.zhang.util.JwtUser;
import com.aaa.zhang.util.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {

    /**
     *  的认证管理器
     */
    @Autowired
    AuthenticationManager authenticationManager;

    /**
     * security 验证 用户名和密码
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    JwtUtils jwtUtils;

    @PostMapping("login")
    public Result createAuthenticationToken(@RequestBody LoginDTO loginDTO) throws Exception {
        System.out.println("username:"+loginDTO.getUsername()+",password:"+loginDTO.getPassword());
        Result result = authenticate(loginDTO.getUsername(), loginDTO.getPassword());
        if(result == null)
        {
            final JwtUser userDetails = (JwtUser) userDetailsService.loadUserByUsername(loginDTO.getUsername());
            System.out.println("userDetails = " + userDetails.getId());
            //生成token 返回给前台
            final String token = jwtUtils.generateToken(userDetails);
            return Result.ok("成功",token);
        }
        return result;
    }

    /**
     * 通过usernmae 和password 来调用 security来判断这个用户
     * @param username  用户名
     * @param password  密码
     * @throws Exception
     */
    private Result authenticate(String username, String password) throws Exception {
        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        }
        catch (DisabledException e) {
            //用户被禁用异常
            //throw new Exception("USER_DISABLED", e);
            return Result.fail("当前用户已被禁用");
        } catch (BadCredentialsException e) {
            //用户密码不对异常
            //throw new Exception("INVALID_CREDENTIALS", e);
            return Result.fail("密码不正确");
        }
        return null;
    }


    @GetMapping("loginSuccess")
    public String loginSuccess(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        JwtUser principal = (JwtUser) authentication.getPrincipal();
        return "当前用户的id是"+principal.getId();
    }
}
2.15 运行我们刚才搭建的项目

SpringBoot+SpringSecurity +Jwt 实现前后端分离认证 从0到实现_第1张图片
我们不带着token访问 试试 (不带着token 则提示我们未认证)
SpringBoot+SpringSecurity +Jwt 实现前后端分离认证 从0到实现_第2张图片
接下来带着token去访问
SpringBoot+SpringSecurity +Jwt 实现前后端分离认证 从0到实现_第3张图片

2.16 demo下载地址

你可能感兴趣的:(security,Java,SpringBoot)