Result
具有链式编程。
@Setter
@Getter
public class Result {
private Integer code;
private String msg;
private Object data;
public Result(Integer code, String msg){
this.code = code;
this.msg = msg;
}
public static Result ok() {
return new Result(200,"success");
}
public static Result error() {
return new Result(201,"error");
}
public Result code(Integer code) {
this.setCode(code);
return this;
}
public Result msg(String msg) {
this.setMsg(msg);
return this;
}
public Result data(Object data) {
this.setData(data);
return this;
}
}
Redis
FastJsonRedisSerializer
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJsonRedisSerializer(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null){
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0){
return null;
}
String str = new String(bytes,DEFAULT_CHARSET);
return JSON.parseObject(str,clazz);
}
protected JavaType getJavaType(Class<T> clazz){
return TypeFactory.defaultInstance().constructType(clazz);
}
}
RedisConfig
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<Object,Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<>(Object.class);
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash 的 key 也采用 StringRedisSerializer 的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
JwtUtil
public class JwtUtil {
/**
* 有效期
*/
public static final Long JWT_TTL = 60 * 60 * 1000L;
/**
* 密匙明文
*/
public static final String JWT_KEY = "Linge999jutSOFJKJkhFDoe90VJlINji";
public static String getUUID() {
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成 jwt
*
* @param subject token中要存在的数据(json 格式)
* @return
*/
public static String createJWT(String subject) {
// 设置过期时间
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
/**
* 生成 jwt
*
* @param subject token中要存在的数据(json 格式)
* @param ttlMillis token 超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());
return builder.compact();
}
/**
* 生成 token
*
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);
return builder.compact();
}
public static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long exMillis = nowMillis + ttlMillis;
Date exDate = new Date(exMillis);
return Jwts.builder()
// 唯一 id
.setId(uuid)
// 主题 可以是 json 数据
.setSubject(subject)
// 签发者
.setIssuer("linge")
// 签发时间
.setIssuedAt(now)
// 使用 HS256 对称加密算法签名,第二个参数未密匙
.signWith(signatureAlgorithm, secretKey)
.setExpiration(exDate);
}
/**
* 生成加密后的密匙 secretKey
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKeySpec key = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
*/
public static Claims parseJWT(String jwt) {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
User
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
// @JsonIgnore
private String password;
private int age;
}
UserMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
List<String> getRolesById(Integer id);
}
<select id="getRolesById" resultType="java.lang.String">
select name
from user_role ur, `role` r
where ur.rid = r.id
and ur.uid = #{id}
</select>
UserService
用户登录
- 在数据库中查询用户
- 将用户 id 生成 jwt,返回给前端。前端再次访问将携带 jwt 于请求头中
- 将 jwt 存入 redis,logout 时删除。
- 查询用户权限 roles,并存入 redis
public interface UserService extends IService<User> {
void register(User user);
Map<String,String> login(User user);
List<String> getRolesById(Integer id);
}
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
final UserMapper userMapper;
final RedisUtil redisUtil;
public UserServiceImpl(UserMapper userMapper, RedisUtil redisUtil) {
this.userMapper = userMapper;
this.redisUtil = redisUtil;
}
@Override
public void register(User user) {
userMapper.insert(user);
}
@Override
public List<String> getRolesById(Integer id) {
return userMapper.getRolesById(id);
}
@Override
public Map<String,String> login(User user) {
// 1.查询用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,user.getUsername());
wrapper.eq(User::getPassword, user.getPassword());
User u = userMapper.selectOne(wrapper);
if (u == null){
throw new RuntimeException("用户名或密码错误");
}
// 2.将用户 id 生成 jwt
String jwt = JwtUtil.createJWT(u.getId().toString());
Map<String,String> map = new HashMap<>(1);
map.put("token",jwt);
// 3.将 token 存入 redis,用于验证 token 是否过期
redisUtil.set("token:" + u.getId(),jwt);
// 4.查询 用户权限,并存入 redis
List<String> roles = userMapper.getRolesById(u.getId());
redisUtil.set("user:"+ u.getId().toString(),roles);
return map;
}
}
ResponseBody
controller 中的返回数据,同一在此封装为 Result,再返回给前端。
@RestControllerAdvice("com.linge.controller")
public class RespAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
Result ok = Result.ok();
if (null == body) {
return ok;
} else if(body instanceof Result) {
ok = (Result)body;
}
else {
ok.data(body);
}
return ok;
}
}
Interceptor
HandlerInterceptor
- 获取 uri,依次判断请求是否需要权限
- 从请求头中获取 token,若为null,则未登录
- 从 token 中解析出 用户 id,与 redis 中的权限信息对比
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
final
UserService userService;
final RedisUtil redisUtil;
public AuthenticationInterceptor(UserService userService, RedisUtil redisUtil) {
this.userService = userService;
this.redisUtil = redisUtil;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 不同的请求资源,需要不同的权限
if (request.getRequestURI().contains("/logout")) {
String token = request.getHeader("token");
Integer uid = Integer.parseInt(JwtUtil.parseJWT(token).getSubject());
// 删除 token 使之过期
redisUtil.del("token:" + uid);
return true;
} else if (true) {
// 2.获取 token,并判断 是否具有 一定的权限
return hasAuthority(request.getHeader("token"), "admin");
}
return false;
}
private boolean hasAuthority(String token, String authority) {
if (token == null) {
throw new RuntimeException("please login");
}
// 登录即可,无需任何权限
if (authority == null) {
return true;
}
// 2.解析出 用户id ----------------------------------------------
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token error");
}
Integer uid = Integer.parseInt(claims.getSubject());
System.out.println(uid);
// 3.判断 token 是否过期 -----------------------------
if (!(token).equals(redisUtil.get("token:"+uid))){
throw new RuntimeException("token 过期");
}
// 4.查询 用户权限 ----------------------------------------------
List<String> roles;
if (redisUtil.hasKey("user:" + uid)) {
// 查 redis
Object o = redisUtil.get("user:" + uid);
System.out.println(o);
roles = (List<String>) (redisUtil.get("user:" + uid));
} else {
// 查 数据库
roles = userService.getRolesById(uid);
// 存入 redis
redisUtil.set("user:" + uid, roles);
}
// 5.鉴权 --------------------------------------------------------
if (roles.contains(authority)) {
return true;
} else {
throw new RuntimeException("have no authority");
}
}
}
InterceptorConfig
@Component
public class InterceptorConfig extends WebMvcConfigurationSupport {
final
UserService userService;
final RedisUtil redisUtil;
public InterceptorConfig(UserService userService, RedisUtil redisUtil) {
this.userService = userService;
this.redisUtil = redisUtil;
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// 白名单
List<String> list = new ArrayList<>();
list.add("/index");
list.add("/user/register");
list.add("/user/login");
registry.addInterceptor(new AuthenticationInterceptor(userService, redisUtil))
.excludePathPatterns(list)
.addPathPatterns("/**").order(1);
}
}
GlobalExceptionHandler
@RestControllerAdvice
//@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public Result handlerException(HttpServletRequest request,Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
return Result.error().data(e.getMessage());
}
}