diff --git a/eladmin-common/src/main/java/me/zhengjie/config/SwaggerConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/SwaggerConfig.java index 4d4520e9..8f0d0376 100644 --- a/eladmin-common/src/main/java/me/zhengjie/config/SwaggerConfig.java +++ b/eladmin-common/src/main/java/me/zhengjie/config/SwaggerConfig.java @@ -39,6 +39,9 @@ public class SwaggerConfig { @Value("${jwt.header}") private String tokenHeader; + @Value("${jwt.token-start-with}") + private String tokenStartWith; + @Value("${swagger.enabled}") private Boolean enabled; @@ -50,7 +53,7 @@ public class SwaggerConfig { ticketPar.name(tokenHeader).description("token") .modelRef(new ModelRef("string")) .parameterType("header") - .defaultValue("Bearer ") + .defaultValue(tokenStartWith + " ") .required(true) .build(); pars.add(ticketPar.build()); diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java index b9d28c3e..51fa8693 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java @@ -82,11 +82,4 @@ public class EncryptUtils { } return b2; } - - /** - * 密码加密 - */ - public static String encryptPassword(String password){ - return DigestUtils.md5DigestAsHex(password.getBytes()); - } } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java index 0799c968..03bf4d01 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java @@ -3,6 +3,7 @@ package me.zhengjie.utils; import cn.hutool.json.JSONObject; import me.zhengjie.exception.BadRequestException; import org.springframework.http.HttpStatus; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; /** @@ -15,7 +16,7 @@ public class SecurityUtils { public static UserDetails getUserDetails() { UserDetails userDetails; try { - userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } catch (Exception e) { throw new BadRequestException(HttpStatus.UNAUTHORIZED, "登录状态过期"); } @@ -30,13 +31,4 @@ public class SecurityUtils { Object obj = getUserDetails(); 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); - } } diff --git a/eladmin-system/pom.xml b/eladmin-system/pom.xml index f3eda434..c08dd54c 100644 --- a/eladmin-system/pom.xml +++ b/eladmin-system/pom.xml @@ -13,7 +13,7 @@ 核心模块 - 0.9.1 + 0.10.6 @@ -45,7 +45,17 @@ io.jsonwebtoken - jjwt + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + + + io.jsonwebtoken + jjwt-jackson ${jjwt.version} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/RedisServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/RedisServiceImpl.java index 52dfe488..991ef865 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/RedisServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/service/impl/RedisServiceImpl.java @@ -1,8 +1,8 @@ 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.service.RedisService; +import me.zhengjie.modules.security.config.SecurityProperties; import me.zhengjie.utils.FileUtil; import me.zhengjie.utils.PageUtil; import org.springframework.beans.factory.annotation.Value; @@ -27,18 +27,13 @@ import java.util.stream.Collectors; public class RedisServiceImpl implements RedisService { private final RedisTemplate redisTemplate; - + private final SecurityProperties properties; @Value("${loginCode.expiration}") private Long expiration; - @Value("${jwt.online}") - private String onlineKey; - - @Value("${jwt.codeKey}") - private String codeKey; - - public RedisServiceImpl(RedisTemplate redisTemplate) { + public RedisServiceImpl(RedisTemplate redisTemplate, SecurityProperties properties) { this.redisTemplate = redisTemplate; + this.properties = properties; } @Override @@ -59,7 +54,7 @@ public class RedisServiceImpl implements RedisService { Set keys = redisTemplate.keys(key); 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; } RedisVo redisVo = new RedisVo(s, Objects.requireNonNull(redisTemplate.opsForValue().get(s)).toString()); @@ -76,7 +71,7 @@ public class RedisServiceImpl implements RedisService { @Override public void deleteAll() { Set 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 diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java index 55a021a2..eb8e5dd2 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java @@ -1,19 +1,12 @@ package me.zhengjie.modules.security.config; import me.zhengjie.annotation.AnonymousAccess; -import me.zhengjie.modules.security.security.JwtAccessDeniedHandler; -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 me.zhengjie.modules.security.security.*; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; 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.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -23,6 +16,7 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.filter.CorsFilter; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @@ -35,38 +29,23 @@ import java.util.Set; */ @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { - private final JwtAuthenticationEntryPoint unauthorizedHandler; - - private final JwtAccessDeniedHandler accessDeniedHandler; - - private final JwtUserDetailsServiceImpl jwtUserDetailsService; - + private final TokenProvider tokenProvider; + private final CorsFilter corsFilter; + private final JwtAuthenticationEntryPoint authenticationErrorHandler; + private final JwtAccessDeniedHandler jwtAccessDeniedHandler; private final ApplicationContext applicationContext; - /** 自定义基于JWT的安全过滤器 */ - private final JwtAuthorizationTokenFilter authenticationTokenFilter; - - @Value("${jwt.header}") - private String tokenHeader; - - public SecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtAccessDeniedHandler accessDeniedHandler, JwtUserDetailsServiceImpl jwtUserDetailsService, JwtAuthorizationTokenFilter authenticationTokenFilter, ApplicationContext applicationContext) { - this.unauthorizedHandler = unauthorizedHandler; - this.accessDeniedHandler = accessDeniedHandler; - this.jwtUserDetailsService = jwtUserDetailsService; - this.authenticationTokenFilter = authenticationTokenFilter; + 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; } - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth - .userDetailsService(jwtUserDetailsService) - .passwordEncoder(passwordEncoderBean()); - } - @Bean GrantedAuthorityDefaults grantedAuthorityDefaults() { // Remove the ROLE_ prefix @@ -74,16 +53,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { } @Bean - public PasswordEncoder passwordEncoderBean() { + public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 搜寻 匿名标记 url: PreAuthorize("hasAnyRole('anonymous')") 和 PreAuthorize("@el.check('anonymous')") 和 AnonymousAccess @@ -102,12 +75,25 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { httpSecurity // 禁用 CSRF .csrf().disable() + + .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) // 授权异常 - .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() - .exceptionHandling().accessDeniedHandler(accessDeniedHandler).and() + .exceptionHandling() + .authenticationEntryPoint(authenticationErrorHandler) + .accessDeniedHandler(jwtAccessDeniedHandler) + + // 防止iframe 造成跨域 + .and() + .headers() + .frameOptions() + .sameOrigin() + // 不创建会话 - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - // 过滤请求 + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + .and() .authorizeRequests() .antMatchers( HttpMethod.GET, @@ -116,7 +102,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { "/**/*.css", "/**/*.js", "/webSocket/**" - ).anonymous() + ).permitAll() // swagger start .antMatchers("/swagger-ui.html").permitAll() .antMatchers("/swagger-resources/**").permitAll() @@ -126,16 +112,18 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { // 文件 .antMatchers("/avatar/**").permitAll() .antMatchers("/file/**").permitAll() + .antMatchers("/druid/**").permitAll() // 放行OPTIONS请求 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() - .antMatchers("/druid/**").permitAll() // 自定义匿名访问所有url放行 : 允许 匿名和带权限以及登录用户访问 .antMatchers(anonymousUrls.toArray(new String[0])).permitAll() // 所有请求都需要认证 .anyRequest().authenticated() - // 防止iframe 造成跨域 - .and().headers().frameOptions().disable(); - httpSecurity - .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + .and() + .apply(securityConfigurerAdapter()); + } + + private TokenConfigurer securityConfigurerAdapter() { + return new TokenConfigurer(tokenProvider); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityProperties.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityProperties.java new file mode 100644 index 00000000..d8688e74 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityProperties.java @@ -0,0 +1,42 @@ +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 + " "; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthController.java similarity index 54% rename from eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java rename to eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthController.java index 0f30d7a8..7893c6f5 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthController.java @@ -9,24 +9,25 @@ import me.zhengjie.annotation.AnonymousAccess; import me.zhengjie.aop.log.Log; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.monitor.service.RedisService; -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.config.SecurityProperties; +import me.zhengjie.modules.security.security.TokenProvider; +import me.zhengjie.modules.security.security.vo.AuthUser; +import me.zhengjie.modules.security.security.vo.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.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.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; /** * @author Zheng Jie @@ -37,24 +38,22 @@ import javax.servlet.http.HttpServletRequest; @RestController @RequestMapping("/auth") @Api(tags = "系统:系统授权接口") -public class AuthenticationController { - - @Value("${jwt.codeKey}") - private String codeKey; - - private final JwtTokenUtil jwtTokenUtil; +public class AuthController { + private final SecurityProperties properties; private final RedisService redisService; - private final UserDetailsService userDetailsService; - private final OnlineUserService onlineUserService; + private final TokenProvider tokenProvider; + private final AuthenticationManagerBuilder authenticationManagerBuilder; - public AuthenticationController(JwtTokenUtil jwtTokenUtil, RedisService redisService, @Qualifier("jwtUserDetailsServiceImpl") UserDetailsService userDetailsService, OnlineUserService onlineUserService) { - this.jwtTokenUtil = jwtTokenUtil; + public AuthController(SecurityProperties properties, RedisService redisService, UserDetailsService userDetailsService, OnlineUserService onlineUserService, TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) { + this.properties = properties; this.redisService = redisService; this.userDetailsService = userDetailsService; this.onlineUserService = onlineUserService; + this.tokenProvider = tokenProvider; + this.authenticationManagerBuilder = authenticationManagerBuilder; } @Log("用户登录") @@ -62,7 +61,6 @@ public class AuthenticationController { @AnonymousAccess @PostMapping(value = "/login") public ResponseEntity login(@Validated @RequestBody AuthUser authUser, HttpServletRequest request){ - // 查询验证码 String code = redisService.getCodeVal(authUser.getUuid()); // 清除验证码 @@ -73,21 +71,23 @@ public class AuthenticationController { if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) { 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()){ - throw new AccountExpiredException("账号已停用,请联系管理员"); - } + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + 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); - // 返回 token - return ResponseEntity.ok(new AuthInfo(token,jwtUser)); + // 返回 token 与 用户信息 + Map authInfo = new HashMap(2){{ + put("token", properties.getTokenStartWith() + token); + put("user", jwtUser); + }}; + return ResponseEntity.ok(authInfo); } @ApiOperation("获取用户信息") @@ -100,23 +100,28 @@ public class AuthenticationController { @ApiOperation("获取验证码") @AnonymousAccess @GetMapping(value = "/code") - public ImgResult getCode(){ + public ResponseEntity getCode(){ // 算术类型 https://gitee.com/whvse/EasyCaptcha ArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36); // 几位数运算,默认是两位 captcha.setLen(2); - // 获取运算的结果:5 + // 获取运算的结果 String result = captcha.text(); - String uuid = codeKey + IdUtil.simpleUUID(); + String uuid = properties.getCodeKey() + IdUtil.simpleUUID(); redisService.saveCode(uuid,result); - return new ImgResult(captcha.toBase64(),uuid); + // 验证码信息 + Map imgResult = new HashMap(2){{ + put("img", captcha.toBase64()); + put("uuid", uuid); + }}; + return ResponseEntity.ok(imgResult); } @ApiOperation("退出登录") @AnonymousAccess @DeleteMapping(value = "/logout") public ResponseEntity logout(HttpServletRequest request){ - onlineUserService.logout(jwtTokenUtil.getToken(request)); + onlineUserService.logout(tokenProvider.getToken(request)); return new ResponseEntity(HttpStatus.OK); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthInfo.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthInfo.java deleted file mode 100644 index 94bb2ab6..00000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -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; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/ImgResult.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/ImgResult.java deleted file mode 100644 index 488ff9e7..00000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/ImgResult.java +++ /dev/null @@ -1,17 +0,0 @@ -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; -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAccessDeniedHandler.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAccessDeniedHandler.java index d510e330..fc9ea698 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAccessDeniedHandler.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAccessDeniedHandler.java @@ -8,6 +8,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +/** + * @author Zheng Jie + */ @Component public class JwtAccessDeniedHandler implements AccessDeniedHandler { diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthenticationEntryPoint.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthenticationEntryPoint.java index 61eb2c50..6f33d313 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthenticationEntryPoint.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthenticationEntryPoint.java @@ -13,9 +13,7 @@ import java.io.Serializable; * @author Zheng Jie */ @Component -public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { - - private static final long serialVersionUID = -8970718410437077606L; +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthorizationTokenFilter.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthorizationTokenFilter.java deleted file mode 100644 index 7fe95574..00000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtAuthorizationTokenFilter.java +++ /dev/null @@ -1,66 +0,0 @@ -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); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java new file mode 100644 index 00000000..d78378d8 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java @@ -0,0 +1,24 @@ +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 { + + 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); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java new file mode 100644 index 00000000..bc0a0de1 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java @@ -0,0 +1,66 @@ +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; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java new file mode 100644 index 00000000..30e2059d --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java @@ -0,0 +1,108 @@ +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 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; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthUser.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/vo/AuthUser.java similarity index 84% rename from eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthUser.java rename to eladmin-system/src/main/java/me/zhengjie/modules/security/security/vo/AuthUser.java index 8c3a9608..4c273fb7 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/AuthUser.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/vo/AuthUser.java @@ -1,4 +1,4 @@ -package me.zhengjie.modules.security.security; +package me.zhengjie.modules.security.security.vo; import lombok.Getter; import lombok.Setter; @@ -19,6 +19,8 @@ public class AuthUser { @NotBlank private String password; + private Boolean rememberMe; + private String code; private String uuid = ""; diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtUser.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/vo/JwtUser.java similarity index 96% rename from eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtUser.java rename to eladmin-system/src/main/java/me/zhengjie/modules/security/security/vo/JwtUser.java index bc39e9a1..48c27567 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/JwtUser.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/vo/JwtUser.java @@ -1,4 +1,4 @@ -package me.zhengjie.modules.security.security; +package me.zhengjie.modules.security.security.vo; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/OnlineUser.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/vo/OnlineUser.java similarity index 89% rename from eladmin-system/src/main/java/me/zhengjie/modules/security/security/OnlineUser.java rename to eladmin-system/src/main/java/me/zhengjie/modules/security/security/vo/OnlineUser.java index d0bb036c..e1097890 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/OnlineUser.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/vo/OnlineUser.java @@ -1,4 +1,4 @@ -package me.zhengjie.modules.security.security; +package me.zhengjie.modules.security.security.vo; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtPermissionServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtPermissionServiceImpl.java deleted file mode 100644 index a8df0bd3..00000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtPermissionServiceImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -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 mapToGrantedAuthorities(UserDto user) { - - System.out.println("--------------------loadPermissionByUser:" + user.getUsername() + "---------------------"); - - Set roles = roleRepository.findByUsers_Id(user.getId()); - Set 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()); - } -} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java index f897bf8e..0a1f4943 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java @@ -1,12 +1,12 @@ package me.zhengjie.modules.security.service; -import me.zhengjie.modules.security.security.JwtUser; -import me.zhengjie.modules.security.security.OnlineUser; +import me.zhengjie.modules.security.config.SecurityProperties; +import me.zhengjie.modules.security.security.vo.JwtUser; +import me.zhengjie.modules.security.security.vo.OnlineUser; import me.zhengjie.utils.EncryptUtils; import me.zhengjie.utils.FileUtil; 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; @@ -26,18 +26,20 @@ import java.util.concurrent.TimeUnit; @SuppressWarnings({"unchecked","all"}) public class OnlineUserService { - @Value("${jwt.expiration}") - private Long expiration; - - @Value("${jwt.online}") - private String onlineKey; - + private final SecurityProperties properties; private final RedisTemplate redisTemplate; - public OnlineUserService(RedisTemplate redisTemplate) { + public OnlineUserService(SecurityProperties properties, RedisTemplate redisTemplate) { + this.properties = properties; this.redisTemplate = redisTemplate; } + /** + * 保存在线用户信息 + * @param jwtUser + * @param token + * @param request + */ public void save(JwtUser jwtUser, String token, HttpServletRequest request){ String job = jwtUser.getDept() + "/" + jwtUser.getJob(); String ip = StringUtils.getIp(request); @@ -49,10 +51,16 @@ public class OnlineUserService { } catch (Exception e) { e.printStackTrace(); } - redisTemplate.opsForValue().set(onlineKey + token, onlineUser); - redisTemplate.expire(onlineKey + token,expiration, TimeUnit.MILLISECONDS); + redisTemplate.opsForValue().set(properties.getOnlineKey() + token, onlineUser); + redisTemplate.expire(properties.getOnlineKey() + token,properties.getTokenValidityInSeconds(), TimeUnit.MILLISECONDS); } + /** + * 查询全部数据 + * @param filter + * @param pageable + * @return + */ public Page getAll(String filter, Pageable pageable){ List onlineUsers = getAll(filter); return new PageImpl( @@ -61,8 +69,13 @@ public class OnlineUserService { onlineUsers.size()); } + /** + * 查询全部数据,不分页 + * @param filter + * @return + */ public List getAll(String filter){ - List keys = new ArrayList<>(redisTemplate.keys(onlineKey + "*")); + List keys = new ArrayList<>(redisTemplate.keys(properties.getOnlineKey() + "*")); Collections.reverse(keys); List onlineUsers = new ArrayList<>(); for (String key : keys) { @@ -81,16 +94,31 @@ public class OnlineUserService { return onlineUsers; } + /** + * 踢出用户 + * @param val + * @throws Exception + */ public void kickOut(String val) throws Exception { - String key = onlineKey + EncryptUtils.desDecrypt(val); + String key = properties.getOnlineKey() + EncryptUtils.desDecrypt(val); redisTemplate.delete(key); } + /** + * 退出登录 + * @param token + */ public void logout(String token) { - String key = onlineKey + token; + String key = properties.getOnlineKey() + token; redisTemplate.delete(key); } + /** + * 导出 + * @param all + * @param response + * @throws IOException + */ public void download(List all, HttpServletResponse response) throws IOException { List> list = new ArrayList<>(); for (OnlineUser user : all) { @@ -105,4 +133,13 @@ public class OnlineUserService { } FileUtil.downloadExcel(list, response); } + + /** + * 查询用户 + * @param key + * @return + */ + public OnlineUser getOne(String key) { + return (OnlineUser)redisTemplate.opsForValue().get(key); + } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtUserDetailsServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java similarity index 72% rename from eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtUserDetailsServiceImpl.java rename to eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java index 0da88846..7bbf5013 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/JwtUserDetailsServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java @@ -1,7 +1,8 @@ package me.zhengjie.modules.security.service; import me.zhengjie.exception.BadRequestException; -import me.zhengjie.modules.security.security.JwtUser; +import me.zhengjie.modules.security.security.vo.JwtUser; +import me.zhengjie.modules.system.service.RoleService; import me.zhengjie.modules.system.service.UserService; import me.zhengjie.modules.system.service.dto.*; import org.springframework.security.core.userdetails.UserDetails; @@ -15,31 +16,33 @@ import java.util.Optional; * @author Zheng Jie * @date 2018-11-22 */ -@Service +@Service("userDetailsService") @Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) -public class JwtUserDetailsServiceImpl implements UserDetailsService { +public class UserDetailsServiceImpl implements UserDetailsService { private final UserService userService; - private final JwtPermissionServiceImpl permissionService; + private final RoleService roleService; - public JwtUserDetailsServiceImpl(UserService userService, JwtPermissionServiceImpl permissionService) { + public UserDetailsServiceImpl(UserService userService, RoleService roleService) { this.userService = userService; - this.permissionService = permissionService; + this.roleService = roleService; } @Override public UserDetails loadUserByUsername(String username){ - UserDto user = userService.findByName(username); if (user == null) { throw new BadRequestException("账号不存在"); } else { + if (!user.getEnabled()) { + throw new BadRequestException("账号未激活"); + } return createJwtUser(user); } } - public UserDetails createJwtUser(UserDto user) { + private UserDetails createJwtUser(UserDto user) { return new JwtUser( user.getId(), user.getUsername(), @@ -49,7 +52,7 @@ public class JwtUserDetailsServiceImpl implements UserDetailsService { user.getPhone(), Optional.ofNullable(user.getDept()).map(DeptSmallDto::getName).orElse(null), Optional.ofNullable(user.getJob()).map(JobSmallDto::getName).orElse(null), - permissionService.mapToGrantedAuthorities(user), + roleService.mapToGrantedAuthorities(user), user.getEnabled(), user.getCreateTime(), user.getLastPasswordResetTime() diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/utils/JwtTokenUtil.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/utils/JwtTokenUtil.java deleted file mode 100644 index 7c4183f6..00000000 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/utils/JwtTokenUtil.java +++ /dev/null @@ -1,134 +0,0 @@ -package me.zhengjie.modules.security.utils; - -import io.jsonwebtoken.*; -import io.jsonwebtoken.impl.DefaultClock; -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; -import java.util.Map; -import java.util.function.Function; - -/** - * @author / - */ -@Component -public class JwtTokenUtil implements Serializable { - - private static final long serialVersionUID = -3301605591108950415L; - private Clock clock = DefaultClock.INSTANCE; - - @Value("${jwt.secret}") - private String secret; - - @Value("${jwt.expiration}") - private Long expiration; - - @Value("${jwt.header}") - private String tokenHeader; - - public String getUsernameFromToken(String token) { - return getClaimFromToken(token, Claims::getSubject); - } - - private Date getIssuedAtDateFromToken(String token) { - return getClaimFromToken(token, Claims::getIssuedAt); - } - - private Date getExpirationDateFromToken(String token) { - return getClaimFromToken(token, Claims::getExpiration); - } - - private T getClaimFromToken(String token, Function claimsResolver) { - final Claims claims = getAllClaimsFromToken(token); - return claimsResolver.apply(claims); - } - - private Claims getAllClaimsFromToken(String token) { - return Jwts.parser() - .setSigningKey(secret) - .parseClaimsJws(token) - .getBody(); - } - - private Boolean isTokenExpired(String token) { - final Date expiration = getExpirationDateFromToken(token); - return expiration.before(clock.now()); - } - - private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { - return (lastPasswordReset != null && created.before(lastPasswordReset)); - } - - private Boolean ignoreTokenExpiration(String token) { - // here you specify tokens, for that the expiration is ignored - return false; - } - - public String generateToken(UserDetails userDetails) { - Map claims = new HashMap<>(16); - return doGenerateToken(claims, userDetails.getUsername()); - } - - private String doGenerateToken(Map claims, String subject) { - final Date createdDate = clock.now(); - final Date expirationDate = calculateExpirationDate(createdDate); - - return Jwts.builder() - .setClaims(claims) - .setSubject(subject) - .setIssuedAt(createdDate) - .setExpiration(expirationDate) - .signWith(SignatureAlgorithm.HS512, secret) - .compact(); - } - - public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) { - final Date created = getIssuedAtDateFromToken(token); - return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset) - && (!isTokenExpired(token) || ignoreTokenExpiration(token)); - } - - public String refreshToken(String token) { - final Date createdDate = clock.now(); - final Date expirationDate = calculateExpirationDate(createdDate); - - final Claims claims = getAllClaimsFromToken(token); - claims.setIssuedAt(createdDate); - claims.setExpiration(expirationDate); - - return Jwts.builder() - .setClaims(claims) - .signWith(SignatureAlgorithm.HS512, secret) - .compact(); - } - - public String getToken(HttpServletRequest request){ - final String requestHeader = request.getHeader(tokenHeader); - String startsWith = "Bearer "; - if (requestHeader != null && requestHeader.startsWith(startsWith)) { - return requestHeader.substring(7); - } - return null; - } - - public Boolean validateToken(String token, UserDetails userDetails) { - JwtUser user = (JwtUser) userDetails; - final Date created = getIssuedAtDateFromToken(token); -// final Date expiration = getExpirationDateFromToken(token); -// 如果token存在,且token创建日期 > 最后修改密码的日期 则代表token有效 - return (!isTokenExpired(token) - && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()) - ); - } - - private Date calculateExpirationDate(Date createdDate) { - return new Date(createdDate.getTime() + expiration); - } -} - diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/MenuRepository.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/MenuRepository.java index 97f4b111..aeffb601 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/MenuRepository.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/repository/MenuRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; /** * @author Zheng Jie @@ -36,9 +37,9 @@ public interface MenuRepository extends JpaRepository, JpaSpecificat /** * 根据角色ID与菜单类型查询菜单 - * @param id roleID + * @param roleIds roleIDs * @param type 类型 * @return / */ - LinkedHashSet findByRoles_IdAndTypeIsNotInOrderBySortAsc(Long id, Integer type); + LinkedHashSet findByRoles_IdInAndTypeNotOrderBySortAsc(Set roleIds, int type); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/RoleController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/RoleController.java index 28b29d7a..5143725a 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/RoleController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/RoleController.java @@ -7,8 +7,10 @@ import me.zhengjie.aop.log.Log; import me.zhengjie.modules.system.domain.Role; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.system.service.RoleService; +import me.zhengjie.modules.system.service.UserService; import me.zhengjie.modules.system.service.dto.RoleQueryCriteria; import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.modules.system.service.dto.UserDto; import me.zhengjie.utils.SecurityUtils; import me.zhengjie.utils.ThrowableUtil; import org.springframework.data.domain.Pageable; @@ -36,11 +38,13 @@ import java.util.stream.Collectors; public class RoleController { private final RoleService roleService; + private final UserService userService; private static final String ENTITY_NAME = "role"; - public RoleController(RoleService roleService) { + public RoleController(RoleService roleService, UserService userService) { this.roleService = roleService; + this.userService = userService; } @ApiOperation("获取单个role") @@ -76,7 +80,8 @@ public class RoleController { @ApiOperation("获取用户级别") @GetMapping(value = "/level") public ResponseEntity getLevel(){ - List levels = roleService.findByUsersId(SecurityUtils.getUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()); + UserDto user = userService.findByName(SecurityUtils.getUsername()); + List levels = roleService.findByUsersId(user.getId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()); return new ResponseEntity<>(Dict.create().set("level", Collections.min(levels)),HttpStatus.OK); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/UserController.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/UserController.java index cc21d304..bc3bb65c 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/UserController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/rest/UserController.java @@ -11,6 +11,7 @@ import me.zhengjie.modules.system.domain.vo.UserPassVo; import me.zhengjie.modules.system.service.DeptService; import me.zhengjie.modules.system.service.RoleService; import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.modules.system.service.dto.UserDto; import me.zhengjie.modules.system.service.dto.UserQueryCriteria; import me.zhengjie.service.VerificationCodeService; import me.zhengjie.utils.*; @@ -20,6 +21,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; @@ -39,17 +42,15 @@ import java.util.stream.Collectors; @RequestMapping("/api/users") public class UserController { + private final PasswordEncoder passwordEncoder; private final UserService userService; - private final DataScope dataScope; - private final DeptService deptService; - private final RoleService roleService; - private final VerificationCodeService verificationCodeService; - public UserController(UserService userService, DataScope dataScope, DeptService deptService, RoleService roleService, VerificationCodeService verificationCodeService) { + public UserController(PasswordEncoder passwordEncoder, UserService userService, DataScope dataScope, DeptService deptService, RoleService roleService, VerificationCodeService verificationCodeService) { + this.passwordEncoder = passwordEncoder; this.userService = userService; this.dataScope = dataScope; this.deptService = deptService; @@ -123,7 +124,8 @@ public class UserController { @DeleteMapping(value = "/{id}") @PreAuthorize("@el.check('user:del')") public ResponseEntity delete(@PathVariable Long id){ - Integer currentLevel = Collections.min(roleService.findByUsersId(SecurityUtils.getUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); + UserDto user = userService.findByName(SecurityUtils.getUsername()); + Integer currentLevel = Collections.min(roleService.findByUsersId(user.getId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); Integer optLevel = Collections.min(roleService.findByUsersId(id).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); if (currentLevel > optLevel) { @@ -135,15 +137,15 @@ public class UserController { @ApiOperation("修改密码") @PostMapping(value = "/updatePass") - public ResponseEntity updatePass(@RequestBody UserPassVo user){ - UserDetails userDetails = SecurityUtils.getUserDetails(); - if(!userDetails.getPassword().equals(EncryptUtils.encryptPassword(user.getOldPass()))){ + public ResponseEntity updatePass(@RequestBody UserPassVo passVo){ + UserDto user = userService.findByName(SecurityUtils.getUsername()); + if(!passwordEncoder.matches(passVo.getOldPass(), user.getPassword())){ throw new BadRequestException("修改失败,旧密码错误"); } - if(userDetails.getPassword().equals(EncryptUtils.encryptPassword(user.getNewPass()))){ + if(passwordEncoder.matches(passVo.getNewPass(), user.getPassword())){ throw new BadRequestException("新密码不能与旧密码相同"); } - userService.updatePass(userDetails.getUsername(),EncryptUtils.encryptPassword(user.getNewPass())); + userService.updatePass(user.getUsername(),passwordEncoder.encode(passVo.getNewPass())); return new ResponseEntity(HttpStatus.OK); } @@ -158,13 +160,13 @@ public class UserController { @ApiOperation("修改邮箱") @PostMapping(value = "/updateEmail/{code}") public ResponseEntity updateEmail(@PathVariable String code,@RequestBody User user){ - UserDetails userDetails = SecurityUtils.getUserDetails(); - if(!userDetails.getPassword().equals(EncryptUtils.encryptPassword(user.getPassword()))){ + UserDto userDto = userService.findByName(SecurityUtils.getUsername()); + if(!passwordEncoder.matches(user.getPassword(), userDto.getPassword())){ throw new BadRequestException("密码错误"); } VerificationCode verificationCode = new VerificationCode(code, ElAdminConstant.RESET_MAIL,"email",user.getEmail()); verificationCodeService.validated(verificationCode); - userService.updateEmail(userDetails.getUsername(),user.getEmail()); + userService.updateEmail(userDto.getUsername(),user.getEmail()); return new ResponseEntity(HttpStatus.OK); } @@ -173,7 +175,8 @@ public class UserController { * @param resources / */ private void checkLevel(User resources) { - Integer currentLevel = Collections.min(roleService.findByUsersId(SecurityUtils.getUserId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); + UserDto user = userService.findByName(SecurityUtils.getUsername()); + Integer currentLevel = Collections.min(roleService.findByUsersId(user.getId()).stream().map(RoleSmallDto::getLevel).collect(Collectors.toList())); Integer optLevel = roleService.findByRoles(resources.getRoles()); if (currentLevel > optLevel) { throw new BadRequestException("角色权限不足"); diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/RoleService.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/RoleService.java index bb4d3558..764122ae 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/RoleService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/RoleService.java @@ -4,10 +4,14 @@ import me.zhengjie.modules.system.domain.Role; import me.zhengjie.modules.system.service.dto.RoleDto; import me.zhengjie.modules.system.service.dto.RoleQueryCriteria; import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.modules.system.service.dto.UserDto; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Pageable; +import org.springframework.security.core.GrantedAuthority; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -99,4 +103,11 @@ public interface RoleService { * @throws IOException / */ void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 获取用户权限信息 + * @param user 用户信息 + * @return 权限信息 + */ + Collection mapToGrantedAuthorities(UserDto user); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MenuServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MenuServiceImpl.java index 5bdbcd23..8abada3a 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MenuServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/MenuServiceImpl.java @@ -66,11 +66,8 @@ public class MenuServiceImpl implements MenuService { @Override public List findByRoles(List roles) { - Set menus = new LinkedHashSet<>(); - for (RoleSmallDto role : roles) { - List menus1 = new ArrayList<>(menuRepository.findByRoles_IdAndTypeIsNotInOrderBySortAsc(role.getId(), 2)); - menus.addAll(menus1); - } + Set roleIds = roles.stream().map(RoleSmallDto::getId).collect(Collectors.toSet()); + LinkedHashSet menus = menuRepository.findByRoles_IdInAndTypeNotOrderBySortAsc(roleIds, 2); return menus.stream().map(menuMapper::toDto).collect(Collectors.toList()); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java index e22b6b88..7f4b4ea0 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java @@ -1,5 +1,6 @@ package me.zhengjie.modules.system.service.impl; +import me.zhengjie.modules.system.domain.Menu; import me.zhengjie.modules.system.domain.Role; import me.zhengjie.exception.EntityExistException; import me.zhengjie.modules.system.repository.RoleRepository; @@ -7,17 +8,17 @@ import me.zhengjie.modules.system.service.RoleService; import me.zhengjie.modules.system.service.dto.RoleDto; import me.zhengjie.modules.system.service.dto.RoleQueryCriteria; import me.zhengjie.modules.system.service.dto.RoleSmallDto; +import me.zhengjie.modules.system.service.dto.UserDto; import me.zhengjie.modules.system.service.mapper.RoleMapper; import me.zhengjie.modules.system.service.mapper.RoleSmallMapper; -import me.zhengjie.utils.FileUtil; -import me.zhengjie.utils.PageUtil; -import me.zhengjie.utils.QueryHelp; -import me.zhengjie.utils.ValidationUtil; +import me.zhengjie.utils.*; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -144,6 +145,20 @@ public class RoleServiceImpl implements RoleService { return Collections.min(roleDtos.stream().map(RoleDto::getLevel).collect(Collectors.toList())); } + @Override + @Cacheable(key = "'loadPermissionByUser:' + #p0.username") + public Collection mapToGrantedAuthorities(UserDto user) { + Set roles = roleRepository.findByUsers_Id(user.getId()); + Set 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(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + @Override public void download(List roles, HttpServletResponse response) throws IOException { List> list = new ArrayList<>(); diff --git a/eladmin-system/src/main/resources/config/application-dev.yml b/eladmin-system/src/main/resources/config/application-dev.yml index d2c26ad3..b19f3017 100644 --- a/eladmin-system/src/main/resources/config/application-dev.yml +++ b/eladmin-system/src/main/resources/config/application-dev.yml @@ -2,7 +2,7 @@ spring: datasource: druid: - type: com.alibaba.druid.pool.DruidDataSource + db-type: com.alibaba.druid.pool.DruidDataSource driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy url: jdbc:log4jdbc:mysql://localhost:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false username: root @@ -44,13 +44,18 @@ spring: #jwt jwt: header: Authorization - secret: mySecret - # token 过期时间/毫秒,6小时 1小时 = 3600000 毫秒 - expiration: 21600000 + # 令牌前缀,主要最后留个空格 + token-start-with: Bearer + # 必须使用最少88位的Base64对该令牌进行编码 + base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= + # 令牌过期时间 此处单位/秒 ,默认4小时 + token-validity-in-seconds: 14400000 + # 记住我模式下的令牌过期时间 此处单位/毫秒 ,默认1天 + token-validity-in-seconds-for-remember-me: 86400000 # 在线用户key - online: online-token + online-key: online-token # 验证码 - codeKey: code-key + code-key: code-key #是否允许生成代码,生产环境设置为false generator: diff --git a/eladmin-system/src/main/resources/config/application-prod.yml b/eladmin-system/src/main/resources/config/application-prod.yml index 5cdd61a1..386e3528 100644 --- a/eladmin-system/src/main/resources/config/application-prod.yml +++ b/eladmin-system/src/main/resources/config/application-prod.yml @@ -2,7 +2,7 @@ spring: datasource: druid: - type: com.alibaba.druid.pool.DruidDataSource + db-type: com.alibaba.druid.pool.DruidDataSource driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy url: jdbc:log4jdbc:mysql://localhost:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false username: root @@ -46,13 +46,18 @@ spring: #jwt jwt: header: Authorization - secret: mySecret - # token 过期时间 2个小时 - expiration: 7200000 + # 令牌前缀,主要最后留个空格 + token-start-with: Bearer + # 必须使用最少88位的Base64对该令牌进行编码 + base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= + # 令牌过期时间 此处单位/秒 ,默认4小时 + token-validity-in-seconds: 14400000 + # 记住我模式下的令牌过期时间 此处单位/毫秒 ,默认1天 + token-validity-in-seconds-for-remember-me: 86400000 # 在线用户key - online: online-token + online-key: online-token # 验证码 - codeKey: code-key + code-key: code-key #是否允许生成代码,生产环境设置为false generator: