提交 ee33a54f 编写于 作者: D dqjdda

新增在线用户管理,新增注销登录功能,token交于redis管理

上级 e245296c
......@@ -37,8 +37,8 @@ public class GeneratorController {
@ApiOperation("查询数据库元数据")
@GetMapping(value = "/tables")
public ResponseEntity getTables(@RequestParam(defaultValue = "") String name,
@RequestParam(defaultValue = "0")Integer page,
@RequestParam(defaultValue = "10")Integer size){
@RequestParam(defaultValue = "0")Integer page,
@RequestParam(defaultValue = "10")Integer size){
int[] startEnd = PageUtil.transToStartEnd(page+1, size);
return new ResponseEntity<>(generatorService.getTables(name,startEnd), HttpStatus.OK);
}
......
......@@ -48,7 +48,7 @@ public class RedisController {
@ApiOperation("清空Redis缓存")
@PreAuthorize("hasAnyRole('ADMIN','REDIS_ALL','REDIS_DELETE')")
public ResponseEntity deleteAll(){
redisService.flushdb();
redisService.deleteAll();
return new ResponseEntity(HttpStatus.OK);
}
}
......@@ -38,7 +38,7 @@ public interface RedisService {
void delete(String key);
/**
* 清空所有缓存
* 清空缓存
*/
void flushdb();
void deleteAll();
}
......@@ -9,16 +9,17 @@ import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author Zheng Jie
* @date 2018-12-10
*/
@Service
@SuppressWarnings({"unchecked","all"})
public class RedisServiceImpl implements RedisService {
private final RedisTemplate redisTemplate;
......@@ -31,18 +32,18 @@ public class RedisServiceImpl implements RedisService {
}
@Override
@SuppressWarnings("unchecked")
public Page<RedisVo> findByKey(String key, Pageable pageable){
List<RedisVo> redisVos = new ArrayList<>();
if(!"*".equals(key)){
key = "*" + key + "*";
}
for (Object s : Objects.requireNonNull(redisTemplate.keys(key))) {
Set<String> keys = redisTemplate.keys(key);
for (String s : keys) {
// 过滤掉权限的缓存
if (s.toString().contains("role::loadPermissionByUser") || s.toString().contains("user::loadUserByUsername")) {
if (s.contains("role::loadPermissionByUser") || s.contains("user::loadUserByUsername") || s.contains("online:token")) {
continue;
}
RedisVo redisVo = new RedisVo(s.toString(), Objects.requireNonNull(redisTemplate.opsForValue().get(s.toString())).toString());
RedisVo redisVo = new RedisVo(s, Objects.requireNonNull(redisTemplate.opsForValue().get(s)).toString());
redisVos.add(redisVo);
}
return new PageImpl<RedisVo>(
......@@ -52,14 +53,14 @@ public class RedisServiceImpl implements RedisService {
}
@Override
@SuppressWarnings("unchecked")
public void delete(String key) {
redisTemplate.delete(key);
}
@Override
public void flushdb() {
Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection().flushDb();
public void deleteAll() {
Set<String> keys = redisTemplate.keys( "*");
redisTemplate.delete(keys.stream().filter(s -> !s.contains("online:token")).collect(Collectors.toList()));
}
@Override
......@@ -72,7 +73,6 @@ public class RedisServiceImpl implements RedisService {
}
@Override
@SuppressWarnings("unchecked")
public void saveCode(String key, Object val) {
redisTemplate.opsForValue().set(key,val);
redisTemplate.expire(key,expiration, TimeUnit.MINUTES);
......
......@@ -89,34 +89,34 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
"/**/*.js"
).anonymous()
.antMatchers(HttpMethod.POST,"/auth/login").anonymous()
.antMatchers(HttpMethod.GET,"/auth/code").anonymous()
.antMatchers(HttpMethod.POST,"/auth/login").permitAll()
.antMatchers(HttpMethod.DELETE,"/auth/logout").permitAll()
.antMatchers(HttpMethod.GET,"/auth/code").permitAll()
// 支付宝回调
.antMatchers("/api/aliPay/return").anonymous()
.antMatchers("/api/aliPay/notify").anonymous()
.antMatchers("/api/aliPay/return").permitAll()
.antMatchers("/api/aliPay/notify").permitAll()
// swagger start
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// swagger end
// 接口限流测试
.antMatchers("/test/**").anonymous()
.antMatchers("/test/**").permitAll()
// 文件
.antMatchers("/avatar/**").anonymous()
.antMatchers("/file/**").anonymous()
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").anonymous()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/druid/**").anonymous()
.antMatchers("/druid/**").permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
// 防止iframe 造成跨域
.and().headers().frameOptions().disable();
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
......
......@@ -12,17 +12,19 @@ import me.zhengjie.modules.security.security.AuthInfo;
import me.zhengjie.modules.security.security.AuthUser;
import me.zhengjie.modules.security.security.ImgResult;
import me.zhengjie.modules.security.security.JwtUser;
import me.zhengjie.modules.security.service.OnlineUserService;
import me.zhengjie.utils.EncryptUtils;
import me.zhengjie.modules.security.utils.JwtTokenUtil;
import me.zhengjie.utils.SecurityUtils;
import me.zhengjie.utils.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* @author Zheng Jie
......@@ -35,25 +37,25 @@ import org.springframework.web.bind.annotation.*;
@Api(tags = "系统:系统授权接口")
public class AuthenticationController {
@Value("${jwt.header}")
private String tokenHeader;
private final JwtTokenUtil jwtTokenUtil;
private final RedisService redisService;
private final UserDetailsService userDetailsService;
public AuthenticationController(JwtTokenUtil jwtTokenUtil, RedisService redisService, @Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService) {
private final OnlineUserService onlineUserService;
public AuthenticationController(JwtTokenUtil jwtTokenUtil, RedisService redisService, @Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, OnlineUserService onlineUserService) {
this.jwtTokenUtil = jwtTokenUtil;
this.redisService = redisService;
this.userDetailsService = userDetailsService;
this.onlineUserService = onlineUserService;
}
@Log("用户登录")
@ApiOperation("登录授权")
@PostMapping(value = "/login")
public ResponseEntity login(@Validated @RequestBody AuthUser authorizationUser){
public ResponseEntity login(@Validated @RequestBody AuthUser authorizationUser, HttpServletRequest request){
// 查询验证码
String code = redisService.getCodeVal(authorizationUser.getUuid());
......@@ -74,10 +76,10 @@ public class AuthenticationController {
if(!jwtUser.isEnabled()){
throw new AccountExpiredException("账号已停用,请联系管理员");
}
// 生成令牌
final String token = jwtTokenUtil.generateToken(jwtUser);
// 保存在线信息
onlineUserService.save(jwtUser, token, request);
// 返回 token
return ResponseEntity.ok(new AuthInfo(token,jwtUser));
}
......@@ -102,4 +104,11 @@ public class AuthenticationController {
redisService.saveCode(uuid,result);
return new ImgResult(captcha.toBase64(),uuid);
}
@ApiOperation("退出登录")
@DeleteMapping(value = "/logout")
public ResponseEntity logout(HttpServletRequest request){
onlineUserService.logout(jwtTokenUtil.getToken(request));
return new ResponseEntity(HttpStatus.OK);
}
}
package me.zhengjie.modules.security.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import me.zhengjie.modules.security.service.OnlineUserService;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth/online")
@Api(tags = "系统:在线用户管理")
public class OnlineController {
private final OnlineUserService onlineUserService;
public OnlineController(OnlineUserService onlineUserService) {
this.onlineUserService = onlineUserService;
}
@ApiOperation("查询在线用户")
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity getAll(String filter, Pageable pageable){
return new ResponseEntity<>(onlineUserService.getAll(filter, pageable),HttpStatus.OK);
}
@ApiOperation("踢出用户")
@DeleteMapping(value = "/{key}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity delete(@PathVariable String key) throws Exception {
onlineUserService.kickOut(key);
return new ResponseEntity(HttpStatus.OK);
}
}
......@@ -3,8 +3,10 @@ package me.zhengjie.modules.security.security;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.security.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
......@@ -21,38 +23,33 @@ import java.io.IOException;
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.online}")
private String onlineKey;
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
private final String tokenHeader;
private final RedisTemplate redisTemplate;
public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.header}") String tokenHeader) {
public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.tokenHeader = tokenHeader;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
final String requestHeader = request.getHeader(this.tokenHeader);
String username = null;
String authToken = null;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
}
String authToken = jwtTokenUtil.getToken(request);
OnlineUser onlineUser = null;
try {
onlineUser = (OnlineUser)redisTemplate.opsForValue().get(onlineKey + authToken);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
if (onlineUser != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// It is not compelling necessary to load the use details from the database. You could also store the information
// in the token and read it from it. It's up to you ;)
JwtUser userDetails = (JwtUser)this.userDetailsService.loadUserByUsername(username);
JwtUser userDetails = (JwtUser)this.userDetailsService.loadUserByUsername(onlineUser.getUserName());
// For simple validation it is completely sufficient to just check the token integrity. You don't have to call
// the database compellingly. Again it's up to you ;)
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
......
package me.zhengjie.modules.monitor.domain.vo;
package me.zhengjie.modules.security.security;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.sql.Timestamp;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @author Zheng Jie
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OnlineUser {
private String userName;
private String job;
private String browser;
private String ip;
private String address;
private Date createTime;
private String key;
private Date loginTime;
private Date lastAccessTime;
}
package me.zhengjie.modules.security.service;
import me.zhengjie.modules.security.security.JwtUser;
import me.zhengjie.modules.security.security.OnlineUser;
import me.zhengjie.utils.EncryptUtils;
import me.zhengjie.utils.PageUtil;
import me.zhengjie.utils.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author Zheng Jie
* @Date 2019年10月26日21:56:27
*/
@Service
@SuppressWarnings({"unchecked","all"})
public class OnlineUserService {
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.online}")
private String onlineKey;
private final RedisTemplate redisTemplate;
public OnlineUserService(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void save(JwtUser jwtUser, String token, HttpServletRequest request){
String job = jwtUser.getDept() + "/" + jwtUser.getJob();
String ip = StringUtils.getIp(request);
String browser = StringUtils.getBrowser(request);
String address = StringUtils.getCityInfo(ip);
OnlineUser onlineUser = null;
try {
onlineUser = new OnlineUser(jwtUser.getUsername(), job, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());
} catch (Exception e) {
e.printStackTrace();
}
redisTemplate.opsForValue().set(onlineKey + token, onlineUser);
redisTemplate.expire(onlineKey + token,expiration, TimeUnit.MILLISECONDS);
}
public Page<OnlineUser> getAll(String filter, Pageable pageable){
List<String> keys = new ArrayList<>(redisTemplate.keys(onlineKey + "*"));
Collections.reverse(keys);
List<OnlineUser> onlineUsers = new ArrayList<>();
for (String key : keys) {
OnlineUser onlineUser = (OnlineUser) redisTemplate.opsForValue().get(key);
if(StringUtils.isNotBlank(filter)){
if(onlineUser.toString().contains(filter)){
onlineUsers.add(onlineUser);
}
} else {
onlineUsers.add(onlineUser);
}
}
Collections.sort(onlineUsers, (o1, o2) -> {
return o2.getLoginTime().compareTo(o1.getLoginTime());
});
return new PageImpl<OnlineUser>(
PageUtil.toPage(pageable.getPageNumber(),pageable.getPageSize(),onlineUsers),
pageable,
keys.size());
}
public void kickOut(String val) throws Exception {
String key = onlineKey + EncryptUtils.desDecrypt(val);
redisTemplate.delete(key);
}
public void logout(String token) {
String key = onlineKey + token;
redisTemplate.delete(key);
}
}
......@@ -6,6 +6,8 @@ import me.zhengjie.modules.security.security.JwtUser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
......@@ -103,6 +105,14 @@ public class JwtTokenUtil implements Serializable {
.compact();
}
public String getToken(HttpServletRequest request){
final String requestHeader = request.getHeader(tokenHeader);
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
return requestHeader.substring(7);
}
return null;
}
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
final Date created = getIssuedAtDateFromToken(token);
......
......@@ -45,8 +45,10 @@ spring:
jwt:
header: Authorization
secret: mySecret
# token 过期时间 6个小时
expiration: 21000000
# token 过期时间/毫秒,6小时 1小时 = 3600000 毫秒
expiration: 21600000
# 在线用户key
online: online-token
#是否允许生成代码,生产环境设置为false
generator:
......
......@@ -49,6 +49,8 @@ jwt:
secret: mySecret
# token 过期时间 2个小时
expiration: 7200000
# 在线用户key
online: online-token
#是否允许生成代码,生产环境设置为false
generator:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册