mirror of https://github.com/elunez/eladmin
新增在线用户管理,新增注销登录功能,token交于redis管理
parent
e245296c7e
commit
ee33a54f7f
|
@ -1,24 +0,0 @@
|
|||
package me.zhengjie.modules.monitor.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author Zheng Jie
|
||||
*/
|
||||
@Data
|
||||
public class OnlineUser {
|
||||
|
||||
private String userName;
|
||||
|
||||
private String browser;
|
||||
|
||||
private String ip;
|
||||
|
||||
private String address;
|
||||
|
||||
private Date createTime;
|
||||
|
||||
private Date lastAccessTime;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
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);
|
||||
String authToken = jwtTokenUtil.getToken(request);
|
||||
OnlineUser onlineUser = null;
|
||||
try {
|
||||
username = jwtTokenUtil.getUsernameFromToken(authToken);
|
||||
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)) {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package me.zhengjie.modules.security.security;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
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 String key;
|
||||
|
||||
private Date loginTime;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
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:
|
||||
|
|
Loading…
Reference in New Issue