具体可查看这篇文章,把JWT总结的很到位 什么是jwt?
<!--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>
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
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;
}
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> {
}
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);
}
}
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;
}
}
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;
}
}
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");
}
}
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));
}
}
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);
}
}
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();
}
}
import lombok.Data;
@Data
public class LoginDTO {
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
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;
}
}
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();
}
}
我们不带着token访问 试试 (不带着token 则提示我们未认证)
接下来带着token去访问