github地址:github地址 下载或克隆点这里!!
本demo是spring security集成jwt进行登录的校验,同时引入redis来存放验证码进行校验工作,利用redis的设置过期时间的特性。
这里只贴出重要的几个,详情请看github.
<!-- jwt依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.mobile</groupId>
<artifactId>spring-mobile-device</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
<!-- redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Swagger依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
重要的依赖差不多就这几个了。
@Data
public class JwtUser implements UserDetails {
private String stuId;
private String password;
public JwtUser(String stuId, String password) {
this.stuId = stuId;
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return stuId;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
其中对于数据库的访问每个人都不同,所以并没有给出代码,需要有一个通过用户输入的用户名来查询用户信息的方法getUserByUserId(userId).
@Service
@Slf4j
public class JwtTokenUtil {
public static final long seriaVersionUID = -3301605591108950415L;
static final String CLAIM_KEY_USERNAME = "sub";
static final String CLAIM_KEY_AUDIENCE = "audience";
static final String CLAIM_KEY_CREATED = "created";
private static final String AUDIENCE_UNKNOWN = "unknown";
private static final String AUDIENCE_WEB = "web";
private static final String AUDIENCE_MOBILE = "mobile";
private static final String AUDIENCE_TABLET = "tablet";
//当前的签名的秘钥
private String secret = "cdd";
//token的有效时间 约25min
private Long expiration = 1296000L;
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
public Date getCreatDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
} catch (Exception e) {
created = null;
}
return created;
}
//得到token的有效期
private Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
public String getAudienceFromToken(String token) {
String audience;
try {
final Claims claims = getClaimsFromToken(token);
audience = (String) claims.get(CLAIM_KEY_AUDIENCE);
} catch (Exception e) {
audience = null;
}
return audience;
}
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
//设置过期时间
private Date generateExpeirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
private Boolean isCreatedAfterTenMinutes(Date created) {
int minutes = (int) ((System.currentTimeMillis() - created.getTime()) / (1000 * 60));
if (minutes >= 10) {
return true;
}
return false;
}
private String generateAudience(Device device) {
String audience = AUDIENCE_UNKNOWN;
if (device.isNormal()) {
audience = AUDIENCE_WEB;
} else if (device.isTablet()) {
audience = AUDIENCE_TABLET;
} else if (device.isMobile()) {
audience = AUDIENCE_MOBILE;
}
return audience;
}
private Boolean ignoreTokenExpiration(String token) {
String audience = getAudienceFromToken(token);
return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience));
}
String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpeirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
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);
}
//判断是否在10分钟后并在有效期内
public Boolean canTokenBeRefreshed(String token) {
final Date created = getCreatDateFromToken(token);
return token != null && created != null && isCreatedAfterTenMinutes(created)
&& (!isTokenExpired(token)) || ignoreTokenExpiration(token);
}
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
log.info("获取要刷新的token: {}", refreshedToken);
return refreshedToken;
}
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getCreatDateFromToken(token);
return (username.equals(user.getUsername())) && !isTokenExpired(token);
}
}
生成五位数验证码
public class RandomUtil {
public static int returnCode(){
Random rand = new Random();
return rand.nextInt(89999)+10000;
}
}
请翻阅我之前的博客
链接:https://blog.csdn.net/qq_43561507/article/details/101025270
个人认为封装较为完整
@RestController
@Slf4j
@RequestMapping("/code")
@Api(tags = "验证码生成接口")
@CrossOrigin
public class SecurityCodeController {
@Autowired
private RedisUtil redisUtil;
@PostMapping("/getCode")
@ApiOperation("获取验证码")
@RoleContro(role = RoleEnum.ADMIN)
public Object getCode(@RequestParam String username){
String optCode = String.valueOf(RandomUtil.returnCode());
redisUtil.set(username,optCode,120);
//这里由于没有用短信发送 为了获取验证码是多少 我们打印到控制台
log.info(optCode);
return ResultVOUtil.success(optCode);
}
}
@Slf4j
@RequestMapping("/admin")
@RestController
@Api(tags = "注册接口")
@CrossOrigin
public class AdminController {
@Autowired
private UserService userService;
@PostMapping("/addUser")
@ApiOperation("添加用户")
@RoleContro(role = RoleEnum.ADMIN)
public Object addUser(UserDTO userDTO) {
return userService.addUser(userDTO);
}
}**
@Slf4j
@RestController
@RequestMapping("/anon")
@Api(tags = "登录接口")
@CrossOrigin
public class AnonController {
@Autowired
private UserService userService;
@ApiOperation("登录")
@PostMapping("/login")
public ResultVO login(@Valid LoginForm loginForm, HttpServletResponse response) {
return userService.login(loginForm, response);
}
}
可能是redis读出乱码的原因 我将他们全部转化为了int类型方便比较 具体原因我还需要检查下 先就这样吧
package com.jwt.redis.service.serviceImpl;
import com.jwt.redis.DTO.UserDTO;
import com.jwt.redis.dao.UserMapper;
import com.jwt.redis.entity.User;
import com.jwt.redis.enums.ResultEnum;
import com.jwt.redis.form.LoginForm;
import com.jwt.redis.security.JwtProperties;
import com.jwt.redis.security.JwtUserDetailServiceImpl;
import com.jwt.redis.service.UserService;
import com.jwt.redis.util.*;
import com.jwt.redis.vo.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
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.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* @author: zty
* @date 2019/9/5 下午4:49
* @description:
*/
@Service
@Slf4j
public class UserServiceImpl implements UserService {
// private final String DEFAULT_PASSWORD = "123456";
@Autowired
private RedisUtil redisUtil;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserMapper userMapper;
@Autowired
private JwtUserDetailServiceImpl jwtUserDetailService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private JwtProperties jwtProperties;
@Override
public User getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();
String key = "anonymousUser";
if (!userName.equals(key)) {
return getUserByUsername(userName);
}
return null;
}
@Override
public User getUserByUsername(String userName) {
return userMapper.selectUserByUsername(userName);
}
/**
* Security自带的
*
* @param loginForm
* @param response
* @return
*/
@Override
public ResultVO login(LoginForm loginForm, HttpServletResponse response) {
int code = Integer.valueOf(loginForm.getSecurityCode());
String code2 = (String) redisUtil.get(loginForm.getUsername());
int redisCode = Integer.valueOf(code2);
if(code==redisCode) {
User user = userMapper.selectUserByUsername(loginForm.getUsername());
if (user == null) {
return ResultVOUtil.error(ResultEnum.USER_NOT_EXIST);
}
UserDetails userDetails = jwtUserDetailService.loadUserByUsername(loginForm.getUsername());
if (!(new BCryptPasswordEncoder().matches(loginForm.getPassword(), userDetails.getPassword()))) {
return ResultVOUtil.error(ResultEnum.PASSWORD_ERROR);
}
Authentication token = new UsernamePasswordAuthenticationToken(loginForm.getUsername(), loginForm.getPassword(), userDetails.getAuthorities());
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
final String realToken = jwtTokenUtil.generateToken(userDetails);
response.addHeader(jwtProperties.getTokenName(), realToken);
Map map = new HashMap();
map.put("role", user.getRole());
map.put("token", realToken);
return ResultVOUtil.success(map);
}
return ResultVOUtil.error(ResultEnum.CODE_ERROR);
}
@Override
public ResultVO addUser(UserDTO userDTO) {
int code = Integer.valueOf(userDTO.getSecurityCode());
String code2 = (String) redisUtil.get(userDTO.getUsername());
int redisCode = Integer.valueOf(code2);
if (code == redisCode) {
User user = new User();
BeanUtils.copyProperties(userDTO, user);
user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
log.info("用户信息" + user);
int result = userMapper.insert(user);
if (result != 1) {
return ResultVOUtil.error(ResultEnum.SQL_ERROR);
}
return ResultVOUtil.success();
}
return ResultVOUtil.error(ResultEnum.CODE_ERROR);
}
}
我用的if来进行校验
如果需要完整的请翻阅我的github
swagger打开有三个接口
先点击获取验证码 (类似于你注册时输入手机号 点击获取验证码 这里我没有写在一起 前端给我整合在一起就行啦)
由于我没有接入云短信的API 我就将随机生成的验证码返回以及打印到控制台来查看
72229
这个验证码有效期120秒
所以我们迅速去注册
注册成功!数据库中有了该条信息(密码已经加密)
登录过程也是一样的 获取验证码 登录