From b54f4e0fcd76e1f1d0f299fc7320fa28075999a2 Mon Sep 17 00:00:00 2001 From: chao Date: Mon, 13 Dec 2021 14:37:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=89=8D=E5=90=8E=E7=AB=AF=E5=88=86=E7=A6=BB?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E8=8F=9C=E5=8D=95=E6=9D=83=E9=99=90=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/AuthenticationProviderImpl.java | 9 +- .../jwt/config/security/ConstantKey.java | 8 + .../security/JwtAuthenticationFilter.java | 119 ++---------- .../jwt/config/security/JwtLoginFilter.java | 42 ++--- .../jwt/config/security/JwtService.java | 173 ++++++++++++++++++ .../jwt/config/security/SecurityConfig.java | 7 +- .../security/UserDetailsServiceImpl.java | 17 +- .../example/jwt/controller/SysUserApi.java | 25 ++- .../java/com/example/jwt/dao/SysUserDao.java | 31 ++++ .../java/com/example/jwt/entity/SysMenu.java | 35 ++++ .../java/com/example/jwt/entity/SysUser.java | 21 +++ .../com/example/jwt/service/RedisService.java | 6 +- .../example/jwt/service/SysUserService.java | 33 ++-- .../jwt/service/impl/SysUserServiceImpl.java | 77 ++++++++ src/main/resources/mapper/SysUserDao.xml | 22 +++ src/main/resources/sys_user.sql | 55 +++++- 16 files changed, 500 insertions(+), 180 deletions(-) create mode 100644 src/main/java/com/example/jwt/config/security/JwtService.java create mode 100644 src/main/java/com/example/jwt/entity/SysMenu.java create mode 100644 src/main/java/com/example/jwt/service/impl/SysUserServiceImpl.java diff --git a/src/main/java/com/example/jwt/config/security/AuthenticationProviderImpl.java b/src/main/java/com/example/jwt/config/security/AuthenticationProviderImpl.java index ce727bb..8d0ecb1 100644 --- a/src/main/java/com/example/jwt/config/security/AuthenticationProviderImpl.java +++ b/src/main/java/com/example/jwt/config/security/AuthenticationProviderImpl.java @@ -6,13 +6,10 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import java.util.ArrayList; - /** * 自定义身份认证验证组件 * @@ -46,12 +43,8 @@ public Authentication authenticate(Authentication authentication) throws Authent UserDetails userDetails = userDetailsService.loadUserByUsername(name); if (bCryptPasswordEncoder.matches(password, userDetails.getPassword())) { log.info("用户登录成功,username={}", name); - // 这里设置权限和角色 - ArrayList authorities = new ArrayList<>(); - authorities.add( new GrantedAuthorityImpl("ROLE_ADMIN")); - authorities.add( new GrantedAuthorityImpl("AUTH_WRITE")); // 生成令牌 这里令牌里面存入了:name,password,authorities, 当然你也可以放其他内容 - return new UsernamePasswordAuthenticationToken(name, password, authorities); + return new UsernamePasswordAuthenticationToken(name, password, userDetails.getAuthorities()); } else { throw new BadCredentialsException("密码错误"); } diff --git a/src/main/java/com/example/jwt/config/security/ConstantKey.java b/src/main/java/com/example/jwt/config/security/ConstantKey.java index 08f694a..42639fb 100644 --- a/src/main/java/com/example/jwt/config/security/ConstantKey.java +++ b/src/main/java/com/example/jwt/config/security/ConstantKey.java @@ -11,4 +11,12 @@ public class ConstantKey { * 签名key */ public static final String SIGNING_KEY = "Charles@Jwt!&Secret^#"; + /** + * Token存入header的键名 + */ + public static final String TOKEN_NAME = "Authorization"; + /** + * Token过期时间(分钟) + */ + public static final Integer TOKEN_EXPIRE = 5; } diff --git a/src/main/java/com/example/jwt/config/security/JwtAuthenticationFilter.java b/src/main/java/com/example/jwt/config/security/JwtAuthenticationFilter.java index b7623de..539d90e 100644 --- a/src/main/java/com/example/jwt/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/jwt/config/security/JwtAuthenticationFilter.java @@ -1,14 +1,11 @@ package com.example.jwt.config.security; -import com.example.jwt.service.RedisService; -import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import org.springframework.util.StringUtils; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -16,8 +13,6 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; /** * 自定义JWT认证过滤器 @@ -32,117 +27,33 @@ @Slf4j public class JwtAuthenticationFilter extends BasicAuthenticationFilter { - private final RedisService redisService; - public JwtAuthenticationFilter(AuthenticationManager authenticationManager, RedisService redisService) { + private final JwtService jwtService; + public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtService jwtService) { super(authenticationManager); - this.redisService = redisService; + this.jwtService = jwtService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - UsernamePasswordAuthenticationToken authentication = getAuthentication(request, response); + UsernamePasswordAuthenticationToken authentication = getAuthentication(request); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } - private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) { - /* - * 解析token - */ - String token = request.getHeader("Authorization"); - if (StringUtils.hasLength(token)) { - String cacheToken = String.valueOf(redisService.get(token)); - if (StringUtils.hasLength(token) && !"null".equals(cacheToken)) { - String user = null; - try { - Claims claims = Jwts.parser() - // 设置生成token的签名key - .setSigningKey(ConstantKey.SIGNING_KEY) - // 解析token - .parseClaimsJws(cacheToken).getBody(); - // 取出用户信息 - user = claims.getSubject(); - // 重设Redis超时时间 - resetRedisExpire(token, claims); - } catch (ExpiredJwtException e) { - log.info("Token过期续签,ExpiredJwtException={}", e.getMessage()); - Claims claims = e.getClaims(); - // 取出用户信息 - user = claims.getSubject(); - // 刷新Token - refreshToken(token, claims); - } catch (UnsupportedJwtException e) { - log.warn("访问[{}]失败,UnsupportedJwtException={}", request.getRequestURI(), e.getMessage()); - } catch (MalformedJwtException e) { - log.warn("访问[{}]失败,MalformedJwtException={}", request.getRequestURI(), e.getMessage()); - } catch (SignatureException e) { - log.warn("访问[{}]失败,SignatureException={}", request.getRequestURI(), e.getMessage()); - } catch (IllegalArgumentException e) { - log.warn("访问[{}]失败,IllegalArgumentException={}", request.getRequestURI(), e.getMessage()); - } - if (user != null) { - // 获取用户权限和角色 - String[] split = user.split("-")[1].split(","); - ArrayList authorities = new ArrayList<>(); - for (String s : split) { - authorities.add(new GrantedAuthorityImpl(s)); - } - // 返回Authentication - return new UsernamePasswordAuthenticationToken(user, null, authorities); - } + private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { + // 解析token + String userinfo = jwtService.parseToken(request); + if (userinfo != null) { + // 获取用户权限和角色 + String[] split = userinfo.split("-")[1].split(","); + ArrayList authorities = new ArrayList<>(); + for (String s : split) { + authorities.add(new GrantedAuthorityImpl(s)); } + // 返回Authentication + return new UsernamePasswordAuthenticationToken(userinfo, null, authorities); } log.warn("访问[{}]失败,需要身份认证", request.getRequestURI()); return null; } - - /** - * 重设Redis超时时间 - * 当前时间 + (`cacheToken`过期时间 - `cacheToken`签发时间) - */ - private void resetRedisExpire(String token, Claims claims) { - // 当前时间 - long current = System.currentTimeMillis(); - // token签发时间 - long issuedAt = claims.getIssuedAt().getTime(); - // token过期时间 - long expiration = claims.getExpiration().getTime(); - // 当前时间 + (`cacheToken`过期时间 - `cacheToken`签发时间) - long expireAt = current + (expiration - issuedAt); - // 重设Redis超时时间 - redisService.expire(token, expireAt); - } - - /** - * 刷新Token - * 刷新Token的时机: 当cacheToken已过期 并且Redis在有效期内 - * 重新生成Token并覆盖Redis的v值(这时候k、v值不一样了),然后设置Redis过期时间为:新Token过期时间 - */ - private void refreshToken(String token, Claims claims) { - // 当前时间 - long current = System.currentTimeMillis(); - /* - * 重新生成token - */ - Calendar calendar = Calendar.getInstance(); - // 设置签发时间 - calendar.setTime(new Date()); - Date now = calendar.getTime(); - // 设置过期时间: 5分钟 - calendar.add(Calendar.MINUTE, 5); - Date time = calendar.getTime(); - String refreshToken = Jwts.builder() - .setSubject(claims.getSubject()) - // 签发时间 - .setIssuedAt(now) - // 过期时间 - .setExpiration(time) - // 算法与签名(同生成token):这里算法采用HS512,常量中定义签名key - .signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY) - .compact(); - // 将refreshToken覆盖Redis的v值,并设置超时时间为refreshToken过期时间 - redisService.set(token, refreshToken, time); - // 打印日志 - log.info("刷新token执行时间: {}", (System.currentTimeMillis() - current) + " 毫秒"); - } } diff --git a/src/main/java/com/example/jwt/config/security/JwtLoginFilter.java b/src/main/java/com/example/jwt/config/security/JwtLoginFilter.java index f786b31..eedbba4 100644 --- a/src/main/java/com/example/jwt/config/security/JwtLoginFilter.java +++ b/src/main/java/com/example/jwt/config/security/JwtLoginFilter.java @@ -2,9 +2,6 @@ import com.alibaba.fastjson.JSON; import com.example.jwt.entity.ResponseJson; -import com.example.jwt.service.RedisService; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -35,10 +32,10 @@ public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; - private final RedisService redisService; - public JwtLoginFilter(AuthenticationManager authenticationManager, RedisService redisService) { + private final JwtService jwtService; + public JwtLoginFilter(AuthenticationManager authenticationManager, JwtService jwtService) { this.authenticationManager = authenticationManager; - this.redisService = redisService; + this.jwtService = jwtService; } /** @@ -65,37 +62,22 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR for (GrantedAuthority grantedAuthority : authorities) { roleList.add(grantedAuthority.getAuthority()); } - /* - * 生成token - */ - Calendar calendar = Calendar.getInstance(); - // 设置签发时间 - calendar.setTime(new Date()); - Date now = calendar.getTime(); - // 设置过期时间: 5分钟 - calendar.add(Calendar.MINUTE, 5); - Date time = calendar.getTime(); - String token = Jwts.builder() - .setSubject(auth.getName() + "-" + roleList) - // 签发时间 - .setIssuedAt(now) - // 过期时间 - .setExpiration(time) - // 自定义算法与签名:这里算法采用HS512,常量中定义签名key - .signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY) - .compact(); - // 将token存入redis,并设置超时时间为token过期时间 - redisService.set(token, token, time); + // 生成token + String token = jwtService.createToken(auth.getName(), roleList); /* * 返回token */ log.info("用户登录成功,生成token={}", token); // 登录成功后,返回token到header里面 response.addHeader("Authorization", token); - // 登录成功后,返回token到body里面 - ResponseJson result = ResponseJson.success("登录成功", token); + // 设置用户信息及token到body里面 + Map resultMap = new HashMap(); + resultMap.put("username",auth.getName()); + resultMap.put("token",token); + resultMap.put("roles",roleList); + ResponseJson> resultJson = ResponseJson.success("登录成功", resultMap); response.setCharacterEncoding("UTF-8"); - response.getWriter().write(JSON.toJSONString(result)); + response.getWriter().write(JSON.toJSONString(resultJson)); } catch (IOException e) { log.error("IOException:", e); } diff --git a/src/main/java/com/example/jwt/config/security/JwtService.java b/src/main/java/com/example/jwt/config/security/JwtService.java new file mode 100644 index 0000000..6a04601 --- /dev/null +++ b/src/main/java/com/example/jwt/config/security/JwtService.java @@ -0,0 +1,173 @@ +package com.example.jwt.config.security; + +import com.example.jwt.service.RedisService; +import io.jsonwebtoken.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +/** + * JWT 服务工具类 + * + * @author : Charles + * @date : 2021/12/13 + */ +@Slf4j +@Service +public class JwtService { + @Resource + private RedisService redisService; + /** + * 生成token + * @param username 用户名 + * @param roleList 角色列表 + */ + public String createToken(String username, List roleList) { + Calendar calendar = Calendar.getInstance(); + // 设置签发时间 + calendar.setTime(new Date()); + Date now = calendar.getTime(); + // 设置过期时间 + calendar.add(Calendar.MINUTE, ConstantKey.TOKEN_EXPIRE); + Date time = calendar.getTime(); + String token = Jwts.builder() + .setSubject(username + "-" + roleList) + // 签发时间 + .setIssuedAt(now) + // 过期时间 + .setExpiration(time) + // 自定义算法与签名:这里算法采用HS512,常量中定义签名key + .signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY) + .compact(); + // 将token存入redis,并设置超时时间为token过期时间 + long expire = time.getTime() - now.getTime(); + redisService.set(token, token, expire); + return token; + } + + /** + * 解析Token + */ + public String parseToken(HttpServletRequest request) { + String userinfo = null; + String token = request.getHeader(ConstantKey.TOKEN_NAME); + if (StringUtils.hasLength(token)) { + String cacheToken = String.valueOf(redisService.get(token)); + if (StringUtils.hasLength(cacheToken) && !"null".equals(cacheToken)) { + try { + Claims claims = Jwts.parser() + // 设置生成token的签名key + .setSigningKey(ConstantKey.SIGNING_KEY) + // 解析token + .parseClaimsJws(cacheToken).getBody(); + // 取出用户信息 + userinfo = claims.getSubject(); + // 重设Redis超时时间 + resetRedisExpire(token, claims); + } catch (ExpiredJwtException e) { + log.info("Token过期续签,ExpiredJwtException={}", e.getMessage()); + Claims claims = e.getClaims(); + // 取出用户信息 + userinfo = claims.getSubject(); + // 刷新Token + refreshToken(token, claims); + } catch (UnsupportedJwtException e) { + log.warn("访问[{}]失败,UnsupportedJwtException={}", request.getRequestURI(), e.getMessage()); + } catch (MalformedJwtException e) { + log.warn("访问[{}]失败,MalformedJwtException={}", request.getRequestURI(), e.getMessage()); + } catch (SignatureException e) { + log.warn("访问[{}]失败,SignatureException={}", request.getRequestURI(), e.getMessage()); + } catch (IllegalArgumentException e) { + log.warn("访问[{}]失败,IllegalArgumentException={}", request.getRequestURI(), e.getMessage()); + } + } + } + return userinfo; + } + + /** + * 解析Token,取出用户名(Token过期仍取出用户名) + */ + public String getUsername(HttpServletRequest request){ + String username = null; + String token = request.getHeader(ConstantKey.TOKEN_NAME); + if (StringUtils.hasLength(token)) { + String userinfo = null; + try { + Claims claims = Jwts.parser() + // 设置生成token的签名key + .setSigningKey(ConstantKey.SIGNING_KEY) + // 解析token + .parseClaimsJws(token).getBody(); + // 取出用户信息 + userinfo = claims.getSubject(); + } catch (ExpiredJwtException e) { + Claims claims = e.getClaims(); + // 取出用户信息 + userinfo = claims.getSubject(); + } catch (Exception ignored){} + if (StringUtils.hasLength(userinfo)){ + username = userinfo.split("-")[0]; + } + } + return username; + } + + + /** + * 重设Redis超时时间 + * 当前时间 + (`cacheToken`过期时间 - `cacheToken`签发时间) + */ + private void resetRedisExpire(String token, Claims claims) { + // 当前时间 + long current = System.currentTimeMillis(); + // token签发时间 + long issuedAt = claims.getIssuedAt().getTime(); + // token过期时间 + long expiration = claims.getExpiration().getTime(); + // 当前时间 + (`cacheToken`过期时间 - `cacheToken`签发时间) + long expireAt = current + (expiration - issuedAt); + // 重设Redis超时时间 + redisService.expire(token, expireAt); + } + + /** + * 刷新Token + * 刷新Token的时机: 当cacheToken已过期 并且Redis在有效期内 + * 重新生成Token并覆盖Redis的v值(这时候k、v值不一样了),然后设置Redis过期时间为:新Token过期时间 + */ + private void refreshToken(String token, Claims claims) { + // 当前时间 + long current = System.currentTimeMillis(); + /* + * 重新生成token + */ + Calendar calendar = Calendar.getInstance(); + // 设置签发时间 + calendar.setTime(new Date()); + Date now = calendar.getTime(); + // 设置过期时间: TOKEN_EXPIRE分钟 + calendar.add(Calendar.MINUTE, ConstantKey.TOKEN_EXPIRE); + Date time = calendar.getTime(); + String refreshToken = Jwts.builder() + .setSubject(claims.getSubject()) + // 签发时间 + .setIssuedAt(now) + // 过期时间 + .setExpiration(time) + // 算法与签名(同生成token):这里算法采用HS512,常量中定义签名key + .signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY) + .compact(); + // 将refreshToken覆盖Redis的v值,并设置超时时间为refreshToken过期时间 + long expire = time.getTime() - now.getTime(); + redisService.set(token, token, expire); + // 打印日志 + log.info("刷新token执行时间: {}", (System.currentTimeMillis() - current) + " 毫秒"); + } +} diff --git a/src/main/java/com/example/jwt/config/security/SecurityConfig.java b/src/main/java/com/example/jwt/config/security/SecurityConfig.java index 2b6e72c..245ac56 100644 --- a/src/main/java/com/example/jwt/config/security/SecurityConfig.java +++ b/src/main/java/com/example/jwt/config/security/SecurityConfig.java @@ -1,6 +1,5 @@ package com.example.jwt.config.security; -import com.example.jwt.service.RedisService; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; @@ -30,7 +29,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource - private RedisService redisService; + private JwtService jwtService; @Resource private UserDetailsService userDetailsService; @@ -68,9 +67,9 @@ protected void configure(HttpSecurity http) throws Exception { .and().authorizeRequests().anyRequest().authenticated() .and() // 自定义JWT登录过滤器 - .addFilter(new JwtLoginFilter(authenticationManager(), redisService)) + .addFilter(new JwtLoginFilter(authenticationManager(), jwtService)) // 自定义JWT认证过滤器 - .addFilter(new JwtAuthenticationFilter(authenticationManager(), redisService)) + .addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtService)) // 自定义认证拦截器,也可以直接使用内置实现类Http403ForbiddenEntryPoint .exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPointImpl()) // 允许跨域 diff --git a/src/main/java/com/example/jwt/config/security/UserDetailsServiceImpl.java b/src/main/java/com/example/jwt/config/security/UserDetailsServiceImpl.java index 524569f..3f9e067 100644 --- a/src/main/java/com/example/jwt/config/security/UserDetailsServiceImpl.java +++ b/src/main/java/com/example/jwt/config/security/UserDetailsServiceImpl.java @@ -2,15 +2,17 @@ import com.example.jwt.dao.SysUserDao; import com.example.jwt.entity.SysUser; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import javax.annotation.Resource; - -import static java.util.Collections.emptyList; +import java.util.ArrayList; +import java.util.List; /** * Description @@ -28,6 +30,15 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx if(user == null){ throw new UsernameNotFoundException("用户" + username + "不存在!"); } - return new User(user.getUsername(), user.getPassword(), emptyList()); + // 获取用户角色列表 + List roleList = sysUserDao.getRoleList(user.getId()); + // 设置权限和角色 + ArrayList authorities = new ArrayList<>(); + for (String role : roleList) { + if (StringUtils.hasLength(role)){ + authorities.add( new GrantedAuthorityImpl(role)); + } + } + return new User(user.getUsername(), user.getPassword(), authorities); } } diff --git a/src/main/java/com/example/jwt/controller/SysUserApi.java b/src/main/java/com/example/jwt/controller/SysUserApi.java index c58d857..103fe0a 100644 --- a/src/main/java/com/example/jwt/controller/SysUserApi.java +++ b/src/main/java/com/example/jwt/controller/SysUserApi.java @@ -1,6 +1,8 @@ package com.example.jwt.controller; +import com.example.jwt.config.security.JwtService; import com.example.jwt.entity.ResponseJson; +import com.example.jwt.entity.SysMenu; import com.example.jwt.entity.SysUser; import com.example.jwt.service.SysUserService; import org.springframework.web.bind.annotation.GetMapping; @@ -8,6 +10,8 @@ import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.List; /** * Description @@ -20,17 +24,36 @@ public class SysUserApi { @Resource private SysUserService sysUserService; - + @Resource + private JwtService jwtService; + /** + * 注册接口 + */ @PostMapping("/register") public ResponseJson register(SysUser sysUser) { return sysUserService.register(sysUser); } + /** + * 获取用户可访问菜单 + */ + @GetMapping("/menu") + public ResponseJson> menuList(HttpServletRequest request) { + String username = jwtService.getUsername(request); + return sysUserService.menuList(username); + } + + /** + * 测试公开接口 + */ @GetMapping("/hello") public ResponseJson hello() { return ResponseJson.success("访问成功!公开接口:/hello",null); } + /** + * 测试需要认证的接口 + */ @GetMapping("/private") public ResponseJson hello2() { return ResponseJson.success("访问成功!非公开接口:/private", null); diff --git a/src/main/java/com/example/jwt/dao/SysUserDao.java b/src/main/java/com/example/jwt/dao/SysUserDao.java index c39f39d..6ebe656 100644 --- a/src/main/java/com/example/jwt/dao/SysUserDao.java +++ b/src/main/java/com/example/jwt/dao/SysUserDao.java @@ -1,7 +1,11 @@ package com.example.jwt.dao; +import com.example.jwt.entity.SysMenu; import com.example.jwt.entity.SysUser; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; /** * Description @@ -11,7 +15,34 @@ */ @Mapper public interface SysUserDao { + + /** + * 根据用户名称查找用户 + */ SysUser findByUsername(String username); + /** + * 新增系统用户 + */ void insertSysUser(SysUser sysUser); + + /** + * 新增用户角色关系表 + */ + void insertUserRoleRelation(Long userId, Long roleId); + + /** + * 获取用户角色名称列表 + */ + List getRoleList(Long userId); + + /** + * 获取用户角色Id列表 + */ + List getRoleIdsByUserId(String username); + + /** + * 根据角色Ids获取菜单列表 + */ + List getMenuListByRoleIds(@Param("roleIds") List roleIds); } diff --git a/src/main/java/com/example/jwt/entity/SysMenu.java b/src/main/java/com/example/jwt/entity/SysMenu.java new file mode 100644 index 0000000..2d02199 --- /dev/null +++ b/src/main/java/com/example/jwt/entity/SysMenu.java @@ -0,0 +1,35 @@ +package com.example.jwt.entity; + +import lombok.Data; + +/** + * Description + * + * @author : Charles + * @date : 2021/12/10 + */ +@Data +public class SysMenu { + /** + * Id + */ + private Long id; + /** + * 菜单名称 + */ + private String menuName; + /** + * 菜单路径 + */ + private String menuPath; + /** + * 菜单类型(1:一级菜单,2:子菜单,3:按钮) + */ + private char menuType; + /** + * 父级菜单Id + */ + private Long parentId; + + private static final long serialVersionUID = 1L; +} diff --git a/src/main/java/com/example/jwt/entity/SysUser.java b/src/main/java/com/example/jwt/entity/SysUser.java index 498cd39..8a00c9c 100644 --- a/src/main/java/com/example/jwt/entity/SysUser.java +++ b/src/main/java/com/example/jwt/entity/SysUser.java @@ -2,6 +2,8 @@ import lombok.Data; +import java.util.List; + /** * Description * @@ -10,7 +12,26 @@ */ @Data public class SysUser { + /** + * 用户Id + */ private Long id; + /** + * 用户名 + */ private String username; + /** + * 密码 + */ private String password; + /** + * 角色Ids,用","隔开 + */ + private String roleIds; + /** + * 角色名称集合 + */ + private List roles; + + private static final long serialVersionUID = 1L; } diff --git a/src/main/java/com/example/jwt/service/RedisService.java b/src/main/java/com/example/jwt/service/RedisService.java index 8b2b500..8eda91a 100644 --- a/src/main/java/com/example/jwt/service/RedisService.java +++ b/src/main/java/com/example/jwt/service/RedisService.java @@ -56,14 +56,14 @@ public boolean set(String key, Object value) { return result; } /** - * 写入缓存 并 加上过期时间 + * 写入缓存 并 加上过期时间(毫秒) */ - public boolean set(String key, Object value, Date date) { + public boolean set(String key, Object value, Long expireTimeMillis) { boolean result = false; try { ValueOperations operations = redisTemplate.opsForValue(); operations.set(key, value); - redisTemplate.expireAt(key, date); + redisTemplate.expire(key, expireTimeMillis, TimeUnit.MILLISECONDS); result = true; } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/com/example/jwt/service/SysUserService.java b/src/main/java/com/example/jwt/service/SysUserService.java index 5332588..be84922 100644 --- a/src/main/java/com/example/jwt/service/SysUserService.java +++ b/src/main/java/com/example/jwt/service/SysUserService.java @@ -1,13 +1,10 @@ package com.example.jwt.service; -import com.example.jwt.dao.SysUserDao; import com.example.jwt.entity.ResponseJson; +import com.example.jwt.entity.SysMenu; import com.example.jwt.entity.SysUser; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import javax.annotation.Resource; +import java.util.List; /** * Description @@ -15,22 +12,14 @@ * @author : Charles * @date : 2021/12/2 */ -@Service -public class SysUserService { - @Resource - private SysUserDao sysUserDao; - - @Resource - private BCryptPasswordEncoder passwordEncoder; - - public ResponseJson register(SysUser sysUser) { - if (StringUtils.hasLength(sysUser.getUsername()) && StringUtils.hasLength(sysUser.getPassword())) { - String encodePassword = passwordEncoder.encode(sysUser.getPassword()); - sysUser.setPassword(encodePassword); - sysUserDao.insertSysUser(sysUser); - return ResponseJson.success("注册成功", sysUser); - } - return ResponseJson.error("用户名或密码不能为空", null); - } +public interface SysUserService { + /** + * 注册接口 + */ + ResponseJson register(SysUser sysUser); + /** + * 获取用户可访问菜单 + */ + ResponseJson> menuList(String username); } diff --git a/src/main/java/com/example/jwt/service/impl/SysUserServiceImpl.java b/src/main/java/com/example/jwt/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..d9a9d4b --- /dev/null +++ b/src/main/java/com/example/jwt/service/impl/SysUserServiceImpl.java @@ -0,0 +1,77 @@ +package com.example.jwt.service.impl; + +import com.example.jwt.dao.SysUserDao; +import com.example.jwt.entity.ResponseJson; +import com.example.jwt.entity.SysMenu; +import com.example.jwt.entity.SysUser; +import com.example.jwt.service.RedisService; +import com.example.jwt.service.SysUserService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.List; + +/** + * Description + * + * @author : Charles + * @date : 2021/12/10 + */ +@Service +public class SysUserServiceImpl implements SysUserService { + + @Resource + private SysUserDao sysUserDao; + + @Resource + private BCryptPasswordEncoder passwordEncoder; + + @Resource + private RedisService redisService; + + @Override + public ResponseJson register(SysUser sysUser) { + if (StringUtils.hasLength(sysUser.getUsername()) && StringUtils.hasLength(sysUser.getPassword())) { + // 密码加密 + String encodePassword = passwordEncoder.encode(sysUser.getPassword()); + sysUser.setPassword(encodePassword); + // 新增用户 + sysUserDao.insertSysUser(sysUser); + // 角色Ids,用","隔开 + String roleIds = sysUser.getRoleIds(); + if (StringUtils.hasLength(roleIds)) { + // 设置用户角色 + String[] split = roleIds.split(","); + for (String s : split) { + if (StringUtils.hasLength(s)) { + // 保存用户角色关系 + sysUserDao.insertUserRoleRelation(sysUser.getId(), Long.valueOf(s)); + } + } + } + return ResponseJson.success("注册成功", sysUser); + } + return ResponseJson.error("用户名或密码不能为空", null); + } + + /** + * 获取用户可访问菜单 + */ + @Override + public ResponseJson> menuList(String username) { + if (!StringUtils.hasLength(username)) { + return ResponseJson.error("用户信息异常", null); + } + // 获取用户角色Id + List roleIds = sysUserDao.getRoleIdsByUserId(username); + List menus = null; + if (!CollectionUtils.isEmpty(roleIds)) { + // 根据角色Id获取菜单列表 + menus = sysUserDao.getMenuListByRoleIds(roleIds); + } + return ResponseJson.success(menus); + } +} diff --git a/src/main/resources/mapper/SysUserDao.xml b/src/main/resources/mapper/SysUserDao.xml index 83fbd0b..a988c14 100644 --- a/src/main/resources/mapper/SysUserDao.xml +++ b/src/main/resources/mapper/SysUserDao.xml @@ -4,7 +4,29 @@ INSERT INTO sys_user(username, password) VALUES(#{username}, #{password}) + + INSERT INTO sys_role_user(user_id, role_id) VALUES(#{userId}, #{roleId}) + + + + diff --git a/src/main/resources/sys_user.sql b/src/main/resources/sys_user.sql index 521bc6a..b61fb53 100644 --- a/src/main/resources/sys_user.sql +++ b/src/main/resources/sys_user.sql @@ -4,14 +4,59 @@ MySQL - 8.0.21 : Database - security ********************************************************************* */ -/*Table structure for table `sys_user` */ - +-- 用户表 DROP TABLE IF EXISTS `sys_user`; - CREATE TABLE `sys_user` ( - `id` bigint NOT NULL AUTO_INCREMENT, + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` varchar(255) DEFAULT NULL COMMENT '用户名', `password` varchar(255) DEFAULT NULL COMMENT '密码', PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统用户表'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表'; +-- 角色表 +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `role_name` varchar(50) DEFAULT NULL COMMENT '角色名称', + `role_desc` varchar(255) DEFAULT NULL COMMENT '描述', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表'; +-- 菜单表 +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `menu_name` varchar(100) DEFAULT NULL COMMENT '菜单名称', + `menu_path` varchar(255) DEFAULT NULL COMMENT '菜单路径', + `menu_type` char DEFAULT NULL COMMENT '菜单类型(1:一级菜单,2:子菜单,3:按钮)', + `menu_parent_id` bigint DEFAULT NULL COMMENT '父级菜单Id', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='菜单表'; +-- 用户&角色 关联表 +DROP TABLE IF EXISTS `sys_role_user`; +CREATE TABLE `sys_role_user` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` varchar(255) DEFAULT NULL COMMENT '用户ID', + `role_id` varchar(50) DEFAULT NULL COMMENT '角色ID', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色关联表'; +-- 菜单&角色 关联表 +DROP TABLE IF EXISTS `sys_role_menu`; +CREATE TABLE `sys_role_menu` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `role_id` varchar(50) DEFAULT NULL COMMENT '角色ID', + `menu_id` varchar(255) DEFAULT NULL COMMENT '菜单ID', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色关联表'; +-- 初始数据,管理员拥有所有菜单权限,普通用户拥有查看权限 +INSERT INTO `sys_role`(`id`, `role_name`, `role_desc`) VALUES (1, 'admin', '管理员'),(2, 'user', '普通用户'); +INSERT INTO `sys_menu`(`id`, `menu_name`,`menu_path`,`menu_type`,`menu_parent_id`) + VALUES (1, '用户管理', '/user', 1, null), + (2, '用户列表', '/user/list', 2, 1), + (3, '新增用户', '/user/add', 2, 1), + (4, '修改用户', '/user/update', 2, 1), + (5, '删除用户', '/user/delete', 3, 1); +INSERT INTO `sys_role_user`(`user_id`, `role_id`) VALUES (1, 1); +INSERT INTO `sys_role_menu`(`role_id`, `menu_id`) + VALUES (1, 1),(1, 2),(1, 3),(1, 4),(1, 5), + (2, 1),(2, 2); +