案例代码:springboot+springsecurity+redis设置新登录后踢出前一个登录用户
生活随笔
收集整理的這篇文章主要介紹了
案例代码:springboot+springsecurity+redis设置新登录后踢出前一个登录用户
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
文章目錄
- pojo層
- security 配置類
- security工具類
- controller:
- service:
pojo層
@Data @AllArgsConstructor @NoArgsConstructor public class OnlineUser {private String userName;private String nickName;private String job;private String browser;private String ip;private String address;private String key;private Date loginTime;}security 配置類
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {private final TokenProvider tokenProvider;private final CorsFilter corsFilter;private final JwtAuthenticationEntryPoint authenticationErrorHandler;private final JwtAccessDeniedHandler jwtAccessDeniedHandler;private final ApplicationContext applicationContext;public SecurityConfig(TokenProvider tokenProvider, CorsFilter corsFilter, JwtAuthenticationEntryPoint authenticationErrorHandler, JwtAccessDeniedHandler jwtAccessDeniedHandler, ApplicationContext applicationContext) {this.tokenProvider = tokenProvider;this.corsFilter = corsFilter;this.authenticationErrorHandler = authenticationErrorHandler;this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;this.applicationContext = applicationContext;}@BeanGrantedAuthorityDefaults grantedAuthorityDefaults() {// 去除 ROLE_ 前綴return new GrantedAuthorityDefaults("");}@Beanpublic PasswordEncoder passwordEncoder() {// 密碼加密方式return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {// 搜尋匿名標記 url: @AnonymousAccessMap<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();Set<String> anonymousUrls = new HashSet<>();for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {HandlerMethod handlerMethod = infoEntry.getValue();AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);if (null != anonymousAccess) {anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());}}httpSecurity// 禁用 CSRF.csrf().disable().addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)// 授權異常.exceptionHandling().authenticationEntryPoint(authenticationErrorHandler).accessDeniedHandler(jwtAccessDeniedHandler)// 防止iframe 造成跨域.and().headers().frameOptions().disable()// 不創建會話.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 靜態資源等等.antMatchers(HttpMethod.GET,"/*.html","/**/*.html","/**/*.css","/**/*.js","/webSocket/**").permitAll()// swagger 文檔.antMatchers("/swagger-ui.html").permitAll().antMatchers("/swagger-resources/**").permitAll().antMatchers("/webjars/**").permitAll().antMatchers("/*/api-docs").permitAll().antMatchers("/v2/api-docs-ext").permitAll()//.antMatchers("/api/wxmp/**").permitAll()// 文件.antMatchers("/avatar/**").permitAll().antMatchers("/file/**").permitAll()// 阿里巴巴 druid.antMatchers("/druid/**").permitAll()// 放行OPTIONS請求.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 所有請求都需要認證.anyRequest().authenticated().and().apply(securityConfigurerAdapter());}private TokenConfigurer securityConfigurerAdapter() {return new TokenConfigurer(tokenProvider);} }其中TokenProvider , JwtAuthenticationEntryPoint,JwtAccessDeniedHandler 均為自定義的類,代碼過多就不貼了
security工具類
public class SecurityUtils {public static UserDetails getUserDetails() {final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication == null) {throw new BadRequestException(HttpStatus.UNAUTHORIZED, "當前登錄狀態過期");}if (authentication.getPrincipal() instanceof UserDetails) {UserDetails userDetails = (UserDetails) authentication.getPrincipal();UserDetailsService userDetailsService = SpringContextHolder.getBean(UserDetailsService.class);return userDetailsService.loadUserByUsername(userDetails.getUsername());}throw new BadRequestException(HttpStatus.UNAUTHORIZED, "找不到當前登錄的信息");}/*** 獲取系統用戶名稱* @return 系統用戶名稱*/public static String getUsername() {final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication == null) {throw new BadRequestException(HttpStatus.UNAUTHORIZED, "當前登錄狀態過期");}UserDetails userDetails = (UserDetails) authentication.getPrincipal();return userDetails.getUsername();}/*** 獲取系統用戶id* @return 系統用戶id*/public static Long getUserId() {Object obj = getUserDetails();JSONObject json = new JSONObject(obj);return json.get("id", Long.class);} }controller:
@RestController @RequestMapping("/auth") @Api(tags = "系統:系統授權接口") public class AuthController {@Value("${loginCode.expiration}")private Long expiration;@Value("${rsa.private_key}")private String privateKey;@Value("${single.login}")private Boolean singleLogin;private final SecurityProperties properties;private final RedisUtils redisUtils;private final UserDetailsService userDetailsService;private final OnlineUserService onlineUserService;private final TokenProvider tokenProvider;private final AuthenticationManagerBuilder authenticationManagerBuilder;public AuthController(SecurityProperties properties, RedisUtils redisUtils, UserDetailsService userDetailsService, OnlineUserService onlineUserService, TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {this.properties = properties;this.redisUtils = redisUtils;this.userDetailsService = userDetailsService;this.onlineUserService = onlineUserService;this.tokenProvider = tokenProvider;this.authenticationManagerBuilder = authenticationManagerBuilder;}@Log("用戶登錄")@ApiOperation("登錄授權")@AnonymousAccess// @AnonymousAccess是自定義注解,用于表示可以匿名訪問的方法@PostMapping(value = "/login")public ResponseEntity<Object> login(@Validated @RequestBody AuthUser authUser, HttpServletRequest request) {// 密碼解密RSA rsa = new RSA(privateKey, null);String password = new String(rsa.decrypt(authUser.getPassword(), KeyType.PrivateKey));// 查詢驗證碼String code = (String) redisUtils.get(authUser.getUuid());// 清除驗證碼redisUtils.del(authUser.getUuid());if (StringUtils.isBlank(code)) {throw new BadRequestException("驗證碼不存在或已過期");}if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {throw new BadRequestException("驗證碼錯誤");}UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);SecurityContextHolder.getContext().setAuthentication(authentication);//上下文設置用戶權限// 生成令牌String token = tokenProvider.createToken(authentication);final JwtUser jwtUser = (JwtUser) authentication.getPrincipal();// 保存在線信息onlineUserService.save(jwtUser, token, request);// 返回 token 與 用戶信息Map<String, Object> authInfo = new HashMap<String, Object>(2) {{put("token", properties.getTokenStartWith() + token);put("user", jwtUser);}};if (singleLogin) {//踢掉之前已經登錄的tokenonlineUserService.checkLoginOnUser(authUser.getUsername(), token);}return ResponseEntity.ok(authInfo);}@ApiOperation("獲取驗證碼")@GetMapping(value = "/code")public ResponseEntity<Object> getCode() {// 算術類型 https://gitee.com/whvse/EasyCaptchaArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36);// 幾位數運算,默認是兩位captcha.setLen(2);// 獲取運算的結果String result = "";try {result = new Double(Double.parseDouble(captcha.text())).intValue() + "";} catch (Exception e) {result = captcha.text();}String uuid = properties.getCodeKey() + IdUtil.simpleUUID();// 保存redisUtils.set(uuid, result, expiration, TimeUnit.MINUTES);// 驗證碼信息Map<String, Object> imgResult = new HashMap<String, Object>(2) {{put("img", captcha.toBase64());put("uuid", uuid);}};return ResponseEntity.ok(imgResult);}@ApiOperation("退出登錄")@DeleteMapping(value = "/logout")public ResponseEntity<Object> logout(HttpServletRequest request) {onlineUserService.logout(tokenProvider.getToken(request));return new ResponseEntity<>(HttpStatus.OK);} }service:
獲得在線用戶列表,與當前登錄的用戶進行匹配,如果匹配到相同的,則刪除之前的
/*** 檢測用戶是否在之前已經登錄,已經登錄踢下線* @param userName 用戶名*/public void checkLoginOnUser(String userName, String igoreToken) {List<OnlineUser> onlineUsers = getAll(userName, 0);if (onlineUsers == null || onlineUsers.isEmpty()) {return;}for (OnlineUser onlineUser : onlineUsers) {if (onlineUser.getUserName().equals(userName)) {try {String token = EncryptUtils.desDecrypt(onlineUser.getKey());if (StringUtils.isNotBlank(igoreToken) && !igoreToken.equals(token)) {this.kickOut(onlineUser.getKey());} else if (StringUtils.isBlank(igoreToken)) {this.kickOut(onlineUser.getKey());}} catch (Exception e) {log.error("checkUser is error", e);}}}}}/*** 踢出用戶* @param key /* @throws Exception /*/public void kickOut(String key) throws Exception {key = properties.getOnlineKey() + EncryptUtils.desDecrypt(key);redisUtils.del(key);}/*** 退出登錄* @param token /*/public void logout(String token) {String key = properties.getOnlineKey() + token;redisUtils.del(key);}其中EncryptUtils.desDecrypt方法:
/*** 對稱解密*/public static String desDecrypt(String source) throws Exception {byte[] src = hex2byte(source.getBytes());DESKeySpec desKeySpec = getDesKeySpec(source);SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");SecretKey secretKey = keyFactory.generateSecret(desKeySpec);cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);byte[] retByte = cipher.doFinal(src);return new String(retByte);}AnonymousAccess 注解的使用對應Security config的抽血方法configure:
@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {// 搜尋匿名標記 url: @AnonymousAccessMap<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();Set<String> anonymousUrls = new HashSet<>();for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {HandlerMethod handlerMethod = infoEntry.getValue();AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);if (null != anonymousAccess) {anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());}}總結
以上是生活随笔為你收集整理的案例代码:springboot+springsecurity+redis设置新登录后踢出前一个登录用户的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot封装统一查询对象进行
- 下一篇: 案例代码:springboot+shir