Commit f0b31a35 authored by dqjdda's avatar dqjdda
Browse files

security 优化,密码加密方式优化采用BCryptPasswordEncoder方式:SHA-256 +随机盐+密钥对密码进行加密

parent fe812f1c
...@@ -39,6 +39,9 @@ public class SwaggerConfig { ...@@ -39,6 +39,9 @@ public class SwaggerConfig {
@Value("${jwt.header}") @Value("${jwt.header}")
private String tokenHeader; private String tokenHeader;
@Value("${jwt.token-start-with}")
private String tokenStartWith;
@Value("${swagger.enabled}") @Value("${swagger.enabled}")
private Boolean enabled; private Boolean enabled;
...@@ -50,7 +53,7 @@ public class SwaggerConfig { ...@@ -50,7 +53,7 @@ public class SwaggerConfig {
ticketPar.name(tokenHeader).description("token") ticketPar.name(tokenHeader).description("token")
.modelRef(new ModelRef("string")) .modelRef(new ModelRef("string"))
.parameterType("header") .parameterType("header")
.defaultValue("Bearer ") .defaultValue(tokenStartWith + " ")
.required(true) .required(true)
.build(); .build();
pars.add(ticketPar.build()); pars.add(ticketPar.build());
......
...@@ -82,11 +82,4 @@ public class EncryptUtils { ...@@ -82,11 +82,4 @@ public class EncryptUtils {
} }
return b2; return b2;
} }
/**
* 密码加密
*/
public static String encryptPassword(String password){
return DigestUtils.md5DigestAsHex(password.getBytes());
}
} }
...@@ -3,6 +3,7 @@ package me.zhengjie.utils; ...@@ -3,6 +3,7 @@ package me.zhengjie.utils;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import me.zhengjie.exception.BadRequestException; import me.zhengjie.exception.BadRequestException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
/** /**
...@@ -15,7 +16,7 @@ public class SecurityUtils { ...@@ -15,7 +16,7 @@ public class SecurityUtils {
public static UserDetails getUserDetails() { public static UserDetails getUserDetails() {
UserDetails userDetails; UserDetails userDetails;
try { try {
userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal(); userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (Exception e) { } catch (Exception e) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED, "登录状态过期"); throw new BadRequestException(HttpStatus.UNAUTHORIZED, "登录状态过期");
} }
...@@ -30,13 +31,4 @@ public class SecurityUtils { ...@@ -30,13 +31,4 @@ public class SecurityUtils {
Object obj = getUserDetails(); Object obj = getUserDetails();
return new JSONObject(obj).get("username", String.class); return new JSONObject(obj).get("username", String.class);
} }
/**
* 获取系统用户id
* @return 系统用户id
*/
public static Long getUserId(){
Object obj = getUserDetails();
return new JSONObject(obj).get("id", Long.class);
}
} }
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<name>核心模块</name> <name>核心模块</name>
<properties> <properties>
<jjwt.version>0.9.1</jjwt.version> <jjwt.version>0.10.6</jjwt.version>
</properties> </properties>
<dependencies> <dependencies>
...@@ -45,7 +45,17 @@ ...@@ -45,7 +45,17 @@
<!--jwt--> <!--jwt-->
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId> <artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version> <version>${jjwt.version}</version>
</dependency> </dependency>
......
package me.zhengjie.modules.monitor.service.impl; package me.zhengjie.modules.monitor.service.impl;
import cn.hutool.core.util.ObjectUtil;
import me.zhengjie.modules.monitor.domain.vo.RedisVo; import me.zhengjie.modules.monitor.domain.vo.RedisVo;
import me.zhengjie.modules.monitor.service.RedisService; import me.zhengjie.modules.monitor.service.RedisService;
import me.zhengjie.modules.security.config.SecurityProperties;
import me.zhengjie.utils.FileUtil; import me.zhengjie.utils.FileUtil;
import me.zhengjie.utils.PageUtil; import me.zhengjie.utils.PageUtil;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
...@@ -27,18 +27,13 @@ import java.util.stream.Collectors; ...@@ -27,18 +27,13 @@ import java.util.stream.Collectors;
public class RedisServiceImpl implements RedisService { public class RedisServiceImpl implements RedisService {
private final RedisTemplate redisTemplate; private final RedisTemplate redisTemplate;
private final SecurityProperties properties;
@Value("${loginCode.expiration}") @Value("${loginCode.expiration}")
private Long expiration; private Long expiration;
@Value("${jwt.online}") public RedisServiceImpl(RedisTemplate redisTemplate, SecurityProperties properties) {
private String onlineKey;
@Value("${jwt.codeKey}")
private String codeKey;
public RedisServiceImpl(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate; this.redisTemplate = redisTemplate;
this.properties = properties;
} }
@Override @Override
...@@ -59,7 +54,7 @@ public class RedisServiceImpl implements RedisService { ...@@ -59,7 +54,7 @@ public class RedisServiceImpl implements RedisService {
Set<String> keys = redisTemplate.keys(key); Set<String> keys = redisTemplate.keys(key);
for (String s : keys) { for (String s : keys) {
// 过滤掉权限的缓存 // 过滤掉权限的缓存
if (s.contains("role::loadPermissionByUser") || s.contains("user::loadUserByUsername") || s.contains(onlineKey) || s.contains(codeKey)) { if (s.contains("role::loadPermissionByUser") || s.contains("user::loadUserByUsername") || s.contains(properties.getOnlineKey()) || s.contains(properties.getCodeKey())) {
continue; continue;
} }
RedisVo redisVo = new RedisVo(s, Objects.requireNonNull(redisTemplate.opsForValue().get(s)).toString()); RedisVo redisVo = new RedisVo(s, Objects.requireNonNull(redisTemplate.opsForValue().get(s)).toString());
...@@ -76,7 +71,7 @@ public class RedisServiceImpl implements RedisService { ...@@ -76,7 +71,7 @@ public class RedisServiceImpl implements RedisService {
@Override @Override
public void deleteAll() { public void deleteAll() {
Set<String> keys = redisTemplate.keys( "*"); Set<String> keys = redisTemplate.keys( "*");
redisTemplate.delete(keys.stream().filter(s -> !s.contains(onlineKey)).filter(s -> !s.contains(codeKey)).collect(Collectors.toList())); redisTemplate.delete(keys.stream().filter(s -> !s.contains(properties.getOnlineKey())).filter(s -> !s.contains(properties.getCodeKey())).collect(Collectors.toList()));
} }
@Override @Override
......
package me.zhengjie.modules.security.config; package me.zhengjie.modules.security.config;
import me.zhengjie.annotation.AnonymousAccess; import me.zhengjie.annotation.AnonymousAccess;
import me.zhengjie.modules.security.security.JwtAccessDeniedHandler; import me.zhengjie.modules.security.security.*;
import me.zhengjie.modules.security.security.JwtAuthenticationEntryPoint;
import me.zhengjie.modules.security.security.JwtAuthorizationTokenFilter;
import me.zhengjie.modules.security.service.JwtUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
...@@ -23,6 +16,7 @@ import org.springframework.security.config.http.SessionCreationPolicy; ...@@ -23,6 +16,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
...@@ -35,38 +29,23 @@ import java.util.Set; ...@@ -35,38 +29,23 @@ import java.util.Set;
*/ */
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter { public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationEntryPoint unauthorizedHandler; private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final JwtAccessDeniedHandler accessDeniedHandler; private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final JwtUserDetailsServiceImpl jwtUserDetailsService;
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
/** 自定义基于JWT的安全过滤器 */ public SecurityConfig(TokenProvider tokenProvider, CorsFilter corsFilter, JwtAuthenticationEntryPoint authenticationErrorHandler, JwtAccessDeniedHandler jwtAccessDeniedHandler, ApplicationContext applicationContext) {
private final JwtAuthorizationTokenFilter authenticationTokenFilter; this.tokenProvider = tokenProvider;
this.corsFilter = corsFilter;
@Value("${jwt.header}") this.authenticationErrorHandler = authenticationErrorHandler;
private String tokenHeader; this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
public SecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtAccessDeniedHandler accessDeniedHandler, JwtUserDetailsServiceImpl jwtUserDetailsService, JwtAuthorizationTokenFilter authenticationTokenFilter, ApplicationContext applicationContext) {
this.unauthorizedHandler = unauthorizedHandler;
this.accessDeniedHandler = accessDeniedHandler;
this.jwtUserDetailsService = jwtUserDetailsService;
this.authenticationTokenFilter = authenticationTokenFilter;
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
} }
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(jwtUserDetailsService)
.passwordEncoder(passwordEncoderBean());
}
@Bean @Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() { GrantedAuthorityDefaults grantedAuthorityDefaults() {
// Remove the ROLE_ prefix // Remove the ROLE_ prefix
...@@ -74,16 +53,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -74,16 +53,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
} }
@Bean @Bean
public PasswordEncoder passwordEncoderBean() { public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override @Override
protected void configure(HttpSecurity httpSecurity) throws Exception { protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻 匿名标记 url: PreAuthorize("hasAnyRole('anonymous')") 和 PreAuthorize("@el.check('anonymous')") 和 AnonymousAccess // 搜寻 匿名标记 url: PreAuthorize("hasAnyRole('anonymous')") 和 PreAuthorize("@el.check('anonymous')") 和 AnonymousAccess
...@@ -102,12 +75,25 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -102,12 +75,25 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
httpSecurity httpSecurity
// 禁用 CSRF // 禁用 CSRF
.csrf().disable() .csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常 // 授权异常
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .exceptionHandling()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler).and() .authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.sameOrigin()
// 不创建会话 // 不创建会话
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .and()
// 过滤请求 .sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests() .authorizeRequests()
.antMatchers( .antMatchers(
HttpMethod.GET, HttpMethod.GET,
...@@ -116,7 +102,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -116,7 +102,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
"/**/*.css", "/**/*.css",
"/**/*.js", "/**/*.js",
"/webSocket/**" "/webSocket/**"
).anonymous() ).permitAll()
// swagger start // swagger start
.antMatchers("/swagger-ui.html").permitAll() .antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll() .antMatchers("/swagger-resources/**").permitAll()
...@@ -126,16 +112,18 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -126,16 +112,18 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 文件 // 文件
.antMatchers("/avatar/**").permitAll() .antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll() .antMatchers("/file/**").permitAll()
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求 // 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/druid/**").permitAll()
// 自定义匿名访问所有url放行 : 允许 匿名和带权限以及登录用户访问 // 自定义匿名访问所有url放行 : 允许 匿名和带权限以及登录用户访问
.antMatchers(anonymousUrls.toArray(new String[0])).permitAll() .antMatchers(anonymousUrls.toArray(new String[0])).permitAll()
// 所有请求都需要认证 // 所有请求都需要认证
.anyRequest().authenticated() .anyRequest().authenticated()
// 防止iframe 造成跨域 .and()
.and().headers().frameOptions().disable(); .apply(securityConfigurerAdapter());
httpSecurity }
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider);
} }
} }
package me.zhengjie.modules.security.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* Jwt参数配置
* @author Zheng Jie
* @date 2019年11月28日
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class SecurityProperties {
/** Request Headers : Authorization */
private String header;
/** 令牌前缀,最后留个空格 Bearer */
private String tokenStartWith;
/** 必须使用最少88位的Base64对该令牌进行编码 */
private String base64Secret;
/** 令牌过期时间 此处单位/秒 */
private Long tokenValidityInSeconds;
/** 记住我模式下的令牌过期时间 此处单位/毫秒 */
private Long tokenValidityInSecondsForRememberMe;
/** 在线用户 key,根据 key 查询 redis 中在线用户的数据 */
private String onlineKey;
/** 验证码 key */
private String codeKey;
public String getTokenStartWith() {
return tokenStartWith + " ";
}
}
...@@ -9,24 +9,25 @@ import me.zhengjie.annotation.AnonymousAccess; ...@@ -9,24 +9,25 @@ import me.zhengjie.annotation.AnonymousAccess;
import me.zhengjie.aop.log.Log; import me.zhengjie.aop.log.Log;
import me.zhengjie.exception.BadRequestException; import me.zhengjie.exception.BadRequestException;
import me.zhengjie.modules.monitor.service.RedisService; import me.zhengjie.modules.monitor.service.RedisService;
import me.zhengjie.modules.security.security.AuthInfo; import me.zhengjie.modules.security.config.SecurityProperties;
import me.zhengjie.modules.security.security.AuthUser; import me.zhengjie.modules.security.security.TokenProvider;
import me.zhengjie.modules.security.security.ImgResult; import me.zhengjie.modules.security.security.vo.AuthUser;
import me.zhengjie.modules.security.security.JwtUser; import me.zhengjie.modules.security.security.vo.JwtUser;
import me.zhengjie.modules.security.service.OnlineUserService; 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.SecurityUtils;
import me.zhengjie.utils.StringUtils; 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.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/** /**
* @author Zheng Jie * @author Zheng Jie
...@@ -37,24 +38,22 @@ import javax.servlet.http.HttpServletRequest; ...@@ -37,24 +38,22 @@ import javax.servlet.http.HttpServletRequest;
@RestController @RestController
@RequestMapping("/auth") @RequestMapping("/auth")
@Api(tags = "系统:系统授权接口") @Api(tags = "系统:系统授权接口")
public class AuthenticationController { public class AuthController {
@Value("${jwt.codeKey}")
private String codeKey;
private final JwtTokenUtil jwtTokenUtil;
private final SecurityProperties properties;
private final RedisService redisService; private final RedisService redisService;
private final UserDetailsService userDetailsService; private final UserDetailsService userDetailsService;
private final OnlineUserService onlineUserService; private final OnlineUserService onlineUserService;
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthenticationController(JwtTokenUtil jwtTokenUtil, RedisService redisService, @Qualifier("jwtUserDetailsServiceImpl") UserDetailsService userDetailsService, OnlineUserService onlineUserService) { public AuthController(SecurityProperties properties, RedisService redisService, UserDetailsService userDetailsService, OnlineUserService onlineUserService, TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.jwtTokenUtil = jwtTokenUtil; this.properties = properties;
this.redisService = redisService; this.redisService = redisService;
this.userDetailsService = userDetailsService; this.userDetailsService = userDetailsService;
this.onlineUserService = onlineUserService; this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
} }
@Log("用户登录") @Log("用户登录")
...@@ -62,7 +61,6 @@ public class AuthenticationController { ...@@ -62,7 +61,6 @@ public class AuthenticationController {
@AnonymousAccess @AnonymousAccess
@PostMapping(value = "/login") @PostMapping(value = "/login")
public ResponseEntity login(@Validated @RequestBody AuthUser authUser, HttpServletRequest request){ public ResponseEntity login(@Validated @RequestBody AuthUser authUser, HttpServletRequest request){
// 查询验证码 // 查询验证码
String code = redisService.getCodeVal(authUser.getUuid()); String code = redisService.getCodeVal(authUser.getUuid());
// 清除验证码 // 清除验证码
...@@ -73,21 +71,23 @@ public class AuthenticationController { ...@@ -73,21 +71,23 @@ public class AuthenticationController {
if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) { if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
throw new BadRequestException("验证码错误"); throw new BadRequestException("验证码错误");
} }
final JwtUser jwtUser = (JwtUser) userDetailsService.loadUserByUsername(authUser.getUsername()); UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(authUser.getUsername(), authUser.getPassword());
if(!jwtUser.getPassword().equals(EncryptUtils.encryptPassword(authUser.getPassword()))){
throw new AccountExpiredException("密码错误");
}
if(!jwtUser.isEnabled()){ Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
throw new AccountExpiredException("账号已停用,请联系管理员"); SecurityContextHolder.getContext().setAuthentication(authentication);
} boolean rememberMe = (authUser.getRememberMe() == null) ? false : authUser.getRememberMe();
// 生成令牌 // 生成令牌
final String token = jwtTokenUtil.generateToken(jwtUser); String token = tokenProvider.createToken(authentication, rememberMe);
final JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
// 保存在线信息 // 保存在线信息
onlineUserService.save(jwtUser, token, request); onlineUserService.save(jwtUser, token, request);
// 返回 token // 返回 token 与 用户信息
return ResponseEntity.ok(new AuthInfo(token,jwtUser)); Map<String,Object> authInfo = new HashMap<String,Object>(2){{
put("token", properties.getTokenStartWith() + token);
put("user", jwtUser);
}};
return ResponseEntity.ok(authInfo);
} }
@ApiOperation("获取用户信息") @ApiOperation("获取用户信息")
...@@ -100,23 +100,28 @@ public class AuthenticationController { ...@@ -100,23 +100,28 @@ public class AuthenticationController {
@ApiOperation("获取验证码") @ApiOperation("获取验证码")
@AnonymousAccess @AnonymousAccess
@GetMapping(value = "/code") @GetMapping(value = "/code")
public ImgResult getCode(){ public ResponseEntity getCode(){
// 算术类型 https://gitee.com/whvse/EasyCaptcha // 算术类型 https://gitee.com/whvse/EasyCaptcha
ArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36); ArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36);
// 几位数运算,默认是两位 // 几位数运算,默认是两位
captcha.setLen(2); captcha.setLen(2);
// 获取运算的结果:5 // 获取运算的结果
String result = captcha.text(); String result = captcha.text();
String uuid = codeKey + IdUtil.simpleUUID(); String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
redisService.saveCode(uuid,result); redisService.saveCode(uuid,result);
return new ImgResult(captcha.toBase64(),uuid); // 验证码信息
Map<String,Object> imgResult = new HashMap<String,Object>(2){{
put("img", captcha.toBase64());
put("uuid", uuid);
}};
return ResponseEntity.ok(imgResult);
} }
@ApiOperation("退出登录") @ApiOperation("退出登录")
@AnonymousAccess @AnonymousAccess
@DeleteMapping(value = "/logout") @DeleteMapping(value = "/logout")
public ResponseEntity logout(HttpServletRequest request){ public ResponseEntity logout(HttpServletRequest request){
onlineUserService.logout(jwtTokenUtil.getToken(request)); onlineUserService.logout(tokenProvider.getToken(request));
return new ResponseEntity(HttpStatus.OK); return new ResponseEntity(HttpStatus.OK);
} }
} }
package me.zhengjie.modules.security.security;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.Serializable;
/**
* @author Zheng Jie
* @date 2018-11-23
* 返回token
*/
@Getter
@AllArgsConstructor
public class AuthInfo implements Serializable {
private final String token;
private final JwtUser user;
}
package me.zhengjie.modules.security.security;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author Zheng Jie
* @date 2019-6-5 17:29:57
*/
@Data
@AllArgsConstructor
public class ImgResult {
private String img;
private String uuid;
}
...@@ -8,6 +8,9 @@ import javax.servlet.http.HttpServletRequest; ...@@ -8,6 +8,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
/**
* @author Zheng Jie
*/
@Component @Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler { public class JwtAccessDeniedHandler implements AccessDeniedHandler {
......
...@@ -13,9 +13,7 @@ import java.io.Serializable; ...@@ -13,9 +13,7 @@ import java.io.Serializable;
* @author Zheng Jie * @author Zheng Jie
*/ */
@Component @Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final long serialVersionUID = -8970718410437077606L;
@Override @Override
public void commence(HttpServletRequest request, public void commence(HttpServletRequest request,
......
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;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Zheng Jie
*/
@Slf4j
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.online}")
private String onlineKey;
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
private final RedisTemplate redisTemplate;
public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsServiceImpl") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authToken = jwtTokenUtil.getToken(request);
OnlineUser onlineUser = null;
try {
onlineUser = (OnlineUser)redisTemplate.opsForValue().get(onlineKey + authToken);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
}
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(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)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
package me.zhengjie.modules.security.security;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author /
*/
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
public TokenConfigurer(TokenProvider tokenProvider){
this.tokenProvider = tokenProvider;
}
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
package me.zhengjie.modules.security.security;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.security.config.SecurityProperties;
import me.zhengjie.modules.security.security.vo.OnlineUser;
import me.zhengjie.modules.security.service.OnlineUserService;
import me.zhengjie.utils.SpringContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @author /
*/
@Slf4j
public class TokenFilter extends GenericFilterBean {
private final TokenProvider tokenProvider;
TokenFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = resolveToken(httpServletRequest);
String requestRri = httpServletRequest.getRequestURI();
// 验证 token 是否存在
OnlineUser onlineUser = null;
try {
SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class);
OnlineUserService onlineUserService = SpringContextHolder.getBean(OnlineUserService.class);
onlineUser = onlineUserService.getOne(properties.getOnlineKey() + token);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
}
if (onlineUser != null && StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestRri);
} else {
log.debug("no valid JWT token found, uri: {}", requestRri);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String resolveToken(HttpServletRequest request) {
SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class);
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
return bearerToken.substring(7);
}
return null;
}
}
package me.zhengjie.modules.security.security;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.security.config.SecurityProperties;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;
/**
* @author /
*/
@Slf4j
@Component
public class TokenProvider implements InitializingBean {
private final SecurityProperties properties;
private static final String AUTHORITIES_KEY = "auth";
private Key key;
public TokenProvider(SecurityProperties properties) {
this.properties = properties;
}
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
this.key = Keys.hmacShaKeyFor(keyBytes);
}
public String createToken(Authentication authentication, boolean rememberMe) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
Date validity;
if (rememberMe) {
validity = new Date(now + properties.getTokenValidityInSecondsForRememberMe());
} else {
validity = new Date(now + properties.getTokenValidityInSeconds());
}
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(authToken);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("Invalid JWT signature.");
e.printStackTrace();
} catch (ExpiredJwtException e) {
log.info("Expired JWT token.");
e.printStackTrace();
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT token.");
e.printStackTrace();
} catch (IllegalArgumentException e) {
log.info("JWT token compact of handler are invalid.");
e.printStackTrace();
}
return false;
}
public String getToken(HttpServletRequest request){
final String requestHeader = request.getHeader(properties.getHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);
}
return null;
}
}
package me.zhengjie.modules.security.security; package me.zhengjie.modules.security.security.vo;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
...@@ -19,6 +19,8 @@ public class AuthUser { ...@@ -19,6 +19,8 @@ public class AuthUser {
@NotBlank @NotBlank
private String password; private String password;
private Boolean rememberMe;
private String code; private String code;
private String uuid = ""; private String uuid = "";
......
package me.zhengjie.modules.security.security; package me.zhengjie.modules.security.security.vo;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
......
package me.zhengjie.modules.security.security; package me.zhengjie.modules.security.security.vo;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
......
package me.zhengjie.modules.security.service;
import me.zhengjie.modules.system.domain.Menu;
import me.zhengjie.modules.system.domain.Role;
import me.zhengjie.modules.system.repository.RoleRepository;
import me.zhengjie.modules.system.service.dto.UserDto;
import me.zhengjie.utils.StringUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Zheng Jie
*/
@Service
@CacheConfig(cacheNames = "role")
public class JwtPermissionServiceImpl {
private final RoleRepository roleRepository;
public JwtPermissionServiceImpl(RoleRepository roleRepository) {
this.roleRepository = roleRepository;
}
/**
* key的名称如有修改,请同步修改 UserServiceImpl 中的 update 方法
* @param user 用户信息
* @return Collection
*/
@Cacheable(key = "'loadPermissionByUser:' + #p0.username")
public Collection<GrantedAuthority> mapToGrantedAuthorities(UserDto user) {
System.out.println("--------------------loadPermissionByUser:" + user.getUsername() + "---------------------");
Set<Role> roles = roleRepository.findByUsers_Id(user.getId());
Set<String> permissions = roles.stream().filter(role -> StringUtils.isNotBlank(role.getPermission())).map(Role::getPermission).collect(Collectors.toSet());
permissions.addAll(
roles.stream().flatMap(role -> role.getMenus().stream())
.filter(menu -> StringUtils.isNotBlank(menu.getPermission()))
.map(Menu::getPermission).collect(Collectors.toSet())
);
return permissions.stream().map(permission -> new SimpleGrantedAuthority(permission))
.collect(Collectors.toList());
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment