Browse Source

spring-boot-demo-rbac-security 基本完成

pull/1/head
Yangkai.Shen 6 years ago
parent
commit
5c0ce04193
20 changed files with 851 additions and 20 deletions
  1. +35
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java
  2. +43
    -1
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java
  3. +82
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtAuthenticationFilter.java
  4. +76
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java
  5. +53
    -4
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java
  6. +6
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityHandlerConfig.java
  7. +32
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/WebMvcConfig.java
  8. +25
    -2
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/AuthController.java
  9. +38
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/TestController.java
  10. +37
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/SecurityException.java
  11. +73
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/handler/GlobalExceptionHandler.java
  12. +7
    -2
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Permission.java
  13. +35
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/LoginRequest.java
  14. +2
    -1
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/PermissionDao.java
  15. +2
    -1
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RoleDao.java
  16. +54
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/CustomUserDetailsService.java
  17. +43
    -9
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java
  18. +21
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java
  19. +36
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/JwtResponse.java
  20. +151
    -0
      spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/UserPrincipal.java

+ 35
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java View File

@@ -0,0 +1,35 @@
package com.xkcoding.rbac.security.common;

/**
* <p>
* 常量池
* </p>
*
* @package: com.xkcoding.rbac.security.common
* @description: 常量池
* @author: yangkai.shen
* @date: Created in 2018-12-10 15:03
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
public interface Consts {
/**
* 启用
*/
Integer ENABLE = 1;
/**
* 禁用
*/
Integer DISABLE = 0;

/**
* 页面
*/
Integer PAGE = 1;

/**
* 按钮
*/
Integer BUTTON = 2;
}

+ 43
- 1
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java View File

@@ -21,6 +21,7 @@ public enum Status implements IStatus {
* 操作成功
*/
SUCCESS(200, "操作成功"),

/**
* 操作异常
*/
@@ -29,7 +30,48 @@ public enum Status implements IStatus {
/**
* 退出成功
*/
LOGOUT(200, "退出成功");
LOGOUT(200, "退出成功"),

/**
* 暂无权限访问
*/
ACCESS_DENIED(403, "权限不足"),

/**
* 请求不存在
*/
REQUEST_NOT_FOUND(404, "请求不存在"),

/**
* 请求异常
*/
BAD_REQUEST(400, "请求异常"),

/**
* 参数不匹配
*/
PARAM_NOT_MATCH(400, "参数不匹配"),

/**
* 参数不能为空
*/
PARAM_NOT_NULL(400,"参数不能为空"),

/**
* 当前用户已被锁定,请联系管理员解锁!
*/
USER_DISABLED(403,"当前用户已被锁定,请联系管理员解锁!"),

/**
* 用户名或密码错误
*/
USERNAME_PASSWORD_ERROR(5001,"用户名或密码错误"),

/**
* token 已过期,请重新登录
*/
TOKEN_EXPIRED(5002,"token 已过期,请重新登录"),
TOKEN_PARSE_ERROR(5002,"token 解析失败,请尝试重新登录");

/**
* 状态码


+ 82
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtAuthenticationFilter.java View File

@@ -0,0 +1,82 @@
package com.xkcoding.rbac.security.config;

import cn.hutool.core.util.StrUtil;
import com.xkcoding.rbac.security.common.Status;
import com.xkcoding.rbac.security.exception.SecurityException;
import com.xkcoding.rbac.security.service.CustomUserDetailsService;
import com.xkcoding.rbac.security.util.JwtUtil;
import com.xkcoding.rbac.security.util.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
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;

/**
* <p>
* Jwt 认证过滤器
* </p>
*
* @package: com.xkcoding.rbac.security.config
* @description: Jwt 认证过滤器
* @author: yangkai.shen
* @date: Created in 2018-12-10 15:15
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private CustomUserDetailsService customUserDetailsService;

@Autowired
private JwtUtil jwtUtil;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
AntPathMatcher antPathMatcher = new AntPathMatcher();
if (antPathMatcher.match("/**/api/auth/**", request.getRequestURI())) {
filterChain.doFilter(request, response);
} else {
String jwt = getJwtFromRequest(request);

if (StrUtil.isNotBlank(jwt)) {
try {
String username = jwtUtil.getUsernameFromJWT(jwt);

UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext()
.setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (SecurityException e) {
ResponseUtil.renderJson(response, e);
}
} else {
ResponseUtil.renderJson(response, Status.ACCESS_DENIED, null);
}
}
}

private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}

+ 76
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java View File

@@ -0,0 +1,76 @@
package com.xkcoding.rbac.security.config;

import cn.hutool.core.util.StrUtil;
import com.xkcoding.rbac.security.common.Consts;
import com.xkcoding.rbac.security.model.Permission;
import com.xkcoding.rbac.security.model.Role;
import com.xkcoding.rbac.security.repository.PermissionDao;
import com.xkcoding.rbac.security.repository.RoleDao;
import com.xkcoding.rbac.security.vo.UserPrincipal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* <p>
* 动态路由认证
* </p>
*
* @package: com.xkcoding.rbac.security.config
* @description: 动态路由认证
* @author: yangkai.shen
* @date: Created in 2018-12-10 17:17
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Component
public class RbacAuthorityService {
@Autowired
private RoleDao roleDao;

@Autowired
private PermissionDao permissionDao;

public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object userInfo = authentication.getPrincipal();
boolean hasPermission = false;

if (userInfo instanceof UserDetails) {
UserPrincipal principal = (UserPrincipal) userInfo;
Long userId = principal.getId();

List<Role> roles = roleDao.selectByUserId(userId);
List<Long> roleIds = roles.stream()
.map(Role::getId)
.collect(Collectors.toList());
List<Permission> permissions = permissionDao.selectByRoleIdList(roleIds);

//获取资源,前后端分离,所以过滤页面权限,只保留按钮权限
List<Permission> btnPerms = permissions.stream()
.filter(permission -> Objects.equals(permission.getType(), Consts.BUTTON))
.filter(permission -> StrUtil.isNotBlank(permission.getPermission()))
.filter(permission -> StrUtil.isNotBlank(permission.getMethod()))
.collect(Collectors.toList());

for (Permission btnPerm : btnPerms) {
AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getPermission(), btnPerm.getMethod());
if (antPathMatcher.matches(request)) {
hasPermission = true;
break;
}
}

return hasPermission;
} else {
return false;
}
}
}

+ 53
- 4
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java View File

@@ -1,11 +1,18 @@
package com.xkcoding.rbac.security.config;

import com.xkcoding.rbac.security.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@@ -28,6 +35,32 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LogoutSuccessHandler logoutSuccessHandler;

@Autowired
private AccessDeniedHandler accessDeniedHandler;

@Autowired
private CustomUserDetailsService customUserDetailsService;

@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(encoder());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
@@ -37,27 +70,43 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.csrf()
.disable()

.formLogin()
.disable()
.httpBasic()
.disable()

// 认证请求
.authorizeRequests()
// 放行 /api/auth/** 的所有请求,参见 AuthController
.antMatchers("/api/auth/**")
.antMatchers("/**/api/auth/**")
.permitAll()
.anyRequest()
.authenticated()
// RBAC 动态 url 认证
.anyRequest()
.access("@rbacAuthorityService.hasPermission(request,authentication)")

// 登出处理
.and()
.logout()
// 登出请求默认为POST请求,改为GET请求
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.logoutRequestMatcher(new AntPathRequestMatcher("/**/logout", "GET"))
// 登出成功处理器
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll()

.and()
// Session 管理
.and()
.sessionManagement()
// 因为使用了JWT,所以这里不管理Session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)

// 异常处理
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler);

// 添加自定义 JWT 过滤器
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}

+ 6
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityHandlerConfig.java View File

@@ -4,6 +4,7 @@ import com.xkcoding.rbac.security.common.Status;
import com.xkcoding.rbac.security.util.ResponseUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

/**
@@ -31,4 +32,9 @@ public class SecurityHandlerConfig {
public LogoutSuccessHandler logoutSuccessHandler() {
return (request, response, authentication) -> ResponseUtil.renderJson(response, Status.LOGOUT, null);
}

@Bean
public AccessDeniedHandler accessDeniedHandler() {
return (request, response, accessDeniedException) -> ResponseUtil.renderJson(response, Status.ACCESS_DENIED, null);
}
}

+ 32
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/WebMvcConfig.java View File

@@ -0,0 +1,32 @@
package com.xkcoding.rbac.security.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* <p>
* MVC配置
* </p>
*
* @package: com.xkcoding.rbac.security.config
* @description: MVC配置
* @author: yangkai.shen
* @date: Created in 2018-12-10 16:09
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

private static final long MAX_AGE_SECS = 3600;

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE")
.maxAge(MAX_AGE_SECS);
}
}

+ 25
- 2
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/AuthController.java View File

@@ -1,11 +1,22 @@
package com.xkcoding.rbac.security.controller;

import com.xkcoding.rbac.security.common.ApiResponse;
import com.xkcoding.rbac.security.payload.LoginRequest;
import com.xkcoding.rbac.security.util.JwtUtil;
import com.xkcoding.rbac.security.vo.JwtResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/**
* <p>
* 认证 Controller,包括用户注册,用户登录请求
@@ -24,11 +35,23 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/api/auth")
public class AuthController {

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private JwtUtil jwtUtil;

/**
* 登录
*/
@PostMapping("/login")
public ApiResponse login() {
return ApiResponse.ofSuccess();
public ApiResponse login(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmailOrPhone(), loginRequest.getPassword()));

SecurityContextHolder.getContext()
.setAuthentication(authentication);

String jwt = jwtUtil.createJWT(authentication);
return ApiResponse.ofSuccess(new JwtResponse(jwt));
}
}

+ 38
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/TestController.java View File

@@ -0,0 +1,38 @@
package com.xkcoding.rbac.security.controller;

import com.xkcoding.rbac.security.common.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* <p>
* 测试Controller
* </p>
*
* @package: com.xkcoding.rbac.security.controller
* @description: 测试Controller
* @author: yangkai.shen
* @date: Created in 2018-12-10 15:44
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping
public ApiResponse list() {
log.info("测试列表查询");
return ApiResponse.ofMessage("测试列表查询");
}

@PostMapping
public ApiResponse add() {
log.info("测试列表添加");
return ApiResponse.ofMessage("测试列表添加");
}
}

+ 37
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/SecurityException.java View File

@@ -0,0 +1,37 @@
package com.xkcoding.rbac.security.exception;

import com.xkcoding.rbac.security.common.BaseException;
import com.xkcoding.rbac.security.common.Status;
import lombok.Getter;

/**
* <p>
* 全局异常
* </p>
*
* @package: com.xkcoding.rbac.security.exception
* @description: 全局异常
* @author: yangkai.shen
* @date: Created in 2018-12-10 17:24
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Getter
public class SecurityException extends BaseException {
public SecurityException(Status status) {
super(status);
}

public SecurityException(Status status, Object data) {
super(status, data);
}

public SecurityException(Integer code, String message) {
super(code, message);
}

public SecurityException(Integer code, String message, Object data) {
super(code, message, data);
}
}

+ 73
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/handler/GlobalExceptionHandler.java View File

@@ -0,0 +1,73 @@
package com.xkcoding.rbac.security.exception.handler;

import cn.hutool.core.collection.CollUtil;
import com.xkcoding.rbac.security.common.ApiResponse;
import com.xkcoding.rbac.security.common.BaseException;
import com.xkcoding.rbac.security.common.Status;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolationException;

/**
* <p>
* 全局统一异常处理
* </p>
*
* @package: com.xkcoding.rbac.security.exception.handler
* @description: 全局统一异常处理
* @author: yangkai.shen
* @date: Created in 2018-12-10 17:00
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

@ExceptionHandler(value = Exception.class)
@ResponseBody
public ApiResponse handlerException(Exception e) {
if (e instanceof NoHandlerFoundException) {
log.error("【全局异常拦截】NoHandlerFoundException: 请求方法 {}, 请求路径 {}", ((NoHandlerFoundException) e).getRequestURL(), ((NoHandlerFoundException) e).getHttpMethod());
return ApiResponse.ofStatus(Status.REQUEST_NOT_FOUND);
} else if (e instanceof MethodArgumentNotValidException) {
log.error("【全局异常拦截】MethodArgumentNotValidException", e);
return ApiResponse.of(Status.BAD_REQUEST.getCode(), ((MethodArgumentNotValidException) e).getBindingResult()
.getAllErrors()
.get(0)
.getDefaultMessage(), null);
} else if (e instanceof ConstraintViolationException) {
log.error("【全局异常拦截】ConstraintViolationException", e);
return ApiResponse.of(Status.BAD_REQUEST.getCode(), CollUtil.getFirst(((ConstraintViolationException) e).getConstraintViolations())
.getMessage(), null);
} else if (e instanceof MethodArgumentTypeMismatchException) {
log.error("【全局异常拦截】MethodArgumentTypeMismatchException: 参数名 {}, 异常信息 {}", ((MethodArgumentTypeMismatchException) e).getName(), ((MethodArgumentTypeMismatchException) e).getMessage());
return ApiResponse.ofStatus(Status.PARAM_NOT_MATCH);
} else if (e instanceof HttpMessageNotReadableException) {
log.error("【全局异常拦截】HttpMessageNotReadableException: 错误信息 {}", ((HttpMessageNotReadableException) e).getMessage());
return ApiResponse.ofStatus(Status.PARAM_NOT_NULL);
} else if (e instanceof BadCredentialsException) {
log.error("【全局异常拦截】BadCredentialsException: 错误信息 {}", e.getMessage());
return ApiResponse.ofStatus(Status.USERNAME_PASSWORD_ERROR);
} else if (e instanceof DisabledException) {
log.error("【全局异常拦截】BadCredentialsException: 错误信息 {}", e.getMessage());
return ApiResponse.ofStatus(Status.USER_DISABLED);
} else if (e instanceof BaseException) {
log.error("【全局异常拦截】DataManagerException: 状态码 {}, 异常信息 {}", ((BaseException) e).getCode(), e.getMessage());
return ApiResponse.ofException((BaseException) e);
}

log.error("【全局异常拦截】: 异常信息 {} ", e.getMessage());
return ApiResponse.ofStatus(Status.ERROR);
}
}

+ 7
- 2
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Permission.java View File

@@ -36,7 +36,7 @@ public class Permission {
private String name;

/**
* 页面地址
* 前端页面地址
*/
private String href;

@@ -46,10 +46,15 @@ public class Permission {
private Integer type;

/**
* 权限表达式
* 后端接口地址
*/
private String permission;

/**
* 后端接口访问方式
*/
private String method;

/**
* 排序
*/


+ 35
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/LoginRequest.java View File

@@ -0,0 +1,35 @@
package com.xkcoding.rbac.security.payload;

import lombok.Data;

import javax.validation.constraints.NotBlank;

/**
* <p>
* 登录请求参数
* </p>
*
* @package: com.xkcoding.rbac.security.payload
* @description: 登录请求参数
* @author: yangkai.shen
* @date: Created in 2018-12-10 15:52
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Data
public class LoginRequest {

/**
* 用户名或邮箱或手机号
*/
@NotBlank
private String usernameOrEmailOrPhone;

/**
* 密码
*/
@NotBlank
private String password;

}

+ 2
- 1
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/PermissionDao.java View File

@@ -4,6 +4,7 @@ import com.xkcoding.rbac.security.model.Permission;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

@@ -29,5 +30,5 @@ public interface PermissionDao extends JpaRepository<Permission, Long>, JpaSpeci
* @return 权限列表
*/
@Query(value = "SELECT DISTINCT sec_permission.* FROM sec_permission,sec_role,sec_role_permission WHERE sec_role.id = sec_role_permission.role_id AND sec_permission.id = sec_role_permission.permission_id AND sec_role.id IN (:ids)", nativeQuery = true)
List<Permission> selectByRoleIdList(List<Long> ids);
List<Permission> selectByRoleIdList(@Param("ids") List<Long> ids);
}

+ 2
- 1
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RoleDao.java View File

@@ -4,6 +4,7 @@ import com.xkcoding.rbac.security.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

@@ -28,5 +29,5 @@ public interface RoleDao extends JpaRepository<Role, Long>, JpaSpecificationExec
* @return 角色列表
*/
@Query(value = "SELECT sec_role.* FROM sec_role,sec_user,sec_user_role WHERE sec_user.id = sec_user_role.user_id AND sec_role.id = sec_user_role.role_id AND sec_user.id = :userId", nativeQuery = true)
List<Role> selectByUserId(Long userId);
List<Role> selectByUserId(@Param("userId") Long userId);
}

+ 54
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/CustomUserDetailsService.java View File

@@ -0,0 +1,54 @@
package com.xkcoding.rbac.security.service;

import com.xkcoding.rbac.security.model.Permission;
import com.xkcoding.rbac.security.model.Role;
import com.xkcoding.rbac.security.model.User;
import com.xkcoding.rbac.security.repository.PermissionDao;
import com.xkcoding.rbac.security.repository.RoleDao;
import com.xkcoding.rbac.security.repository.UserDao;
import com.xkcoding.rbac.security.vo.UserPrincipal;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.List;
import java.util.stream.Collectors;

/**
* <p>
* 自定义UserDetails查询
* </p>
*
* @package: com.xkcoding.rbac.security.service
* @description: 自定义UserDetails查询
* @author: yangkai.shen
* @date: Created in 2018-12-10 10:29
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;

@Autowired
private RoleDao roleDao;

@Autowired
private PermissionDao permissionDao;

@Override
public UserDetails loadUserByUsername(String usernameOrEmailOrPhone) throws UsernameNotFoundException {
User user = userDao.findByUsernameOrEmailOrPhone(usernameOrEmailOrPhone, usernameOrEmailOrPhone, usernameOrEmailOrPhone)
.orElseThrow(() -> new UsernameNotFoundException("未找到用户信息 : " + usernameOrEmailOrPhone));
List<Role> roles = roleDao.selectByUserId(user.getId());
List<Long> roleIds = roles.stream()
.map(Role::getId)
.collect(Collectors.toList());
List<Permission> permissions = permissionDao.selectByRoleIdList(roleIds);
return UserPrincipal.create(user, roles, permissions);
}
}

+ 43
- 9
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java View File

@@ -1,14 +1,21 @@
package com.xkcoding.rbac.security.util;

import cn.hutool.core.date.DateUtil;
import com.xkcoding.rbac.security.common.Status;
import com.xkcoding.rbac.security.config.JwtConfig;
import com.xkcoding.rbac.security.exception.SecurityException;
import com.xkcoding.rbac.security.vo.UserPrincipal;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
* <p>
@@ -37,19 +44,21 @@ public class JwtUtil {
/**
* 创建JWT
*
* @param id 用户id
* @param subject 用户名
* @param roles 用户角色
* @param id 用户id
* @param subject 用户名
* @param roles 用户角色
* @param authorities 用户权限
* @return JWT
*/
public String createJWT(String id, String subject, String roles) {
public String createJWT(Long id, String subject, List<String> roles, Collection<? extends GrantedAuthority> authorities) {
Date now = new Date();
JwtBuilder builder = Jwts.builder()
.setId(id)
.setId(id.toString())
.setSubject(subject)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, jwtConfig.getKey())
.claim("roles", roles);
.claim("roles", roles)
.claim("authorities", authorities);
if (jwtConfig.getTtl() > 0) {
builder.setExpiration(DateUtil.offsetMillisecond(now, jwtConfig.getTtl()
.intValue()));
@@ -57,6 +66,17 @@ public class JwtUtil {
return builder.compact();
}

/**
* 创建JWT
*
* @param authentication 用户认证信息
* @return JWT
*/
public String createJWT(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
return createJWT(userPrincipal.getId(), userPrincipal.getUsername(), userPrincipal.getRoles(), userPrincipal.getAuthorities());
}

/**
* 解析JWT
*
@@ -64,24 +84,38 @@ public class JwtUtil {
* @return {@link Claims}
*/
public Claims parseJWT(String jwt) {
Claims claims = null;
try {
claims = Jwts.parser()
return Jwts.parser()
.setSigningKey(jwtConfig.getKey())
.parseClaimsJws(jwt)
.getBody();
} catch (ExpiredJwtException e) {
log.error("Token 已过期");
throw new SecurityException(Status.TOKEN_EXPIRED);
} catch (UnsupportedJwtException e) {
log.error("不支持的 Token");
throw new SecurityException(Status.TOKEN_PARSE_ERROR);
} catch (MalformedJwtException e) {
log.error("Token 无效");
throw new SecurityException(Status.TOKEN_PARSE_ERROR);
} catch (SignatureException e) {
log.error("无效的 Token 签名");
throw new SecurityException(Status.TOKEN_PARSE_ERROR);
} catch (IllegalArgumentException e) {
log.error("Token 参数不存在");
throw new SecurityException(Status.TOKEN_PARSE_ERROR);
}
return claims;
}

/**
* 根据 jwt 获取用户名
*
* @param jwt JWT
* @return 用户名
*/
public String getUsernameFromJWT(String jwt) {
Claims claims = parseJWT(jwt);
return claims.getSubject();
}

}

+ 21
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java View File

@@ -2,6 +2,7 @@ package com.xkcoding.rbac.security.util;

import cn.hutool.json.JSONUtil;
import com.xkcoding.rbac.security.common.ApiResponse;
import com.xkcoding.rbac.security.common.BaseException;
import com.xkcoding.rbac.security.common.IStatus;
import lombok.extern.slf4j.Slf4j;

@@ -44,4 +45,24 @@ public class ResponseUtil {
log.error("Response写出JSON异常,", e);
}
}

/**
* 往 response 写出 json
*
* @param response 响应
* @param exception 异常
*/
public static void renderJson(HttpServletResponse response, BaseException exception) {
try {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(200);

response.getWriter()
.write(JSONUtil.toJsonStr(ApiResponse.ofException(exception)));
} catch (IOException e) {
log.error("Response写出JSON异常,", e);
}
}
}

+ 36
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/JwtResponse.java View File

@@ -0,0 +1,36 @@
package com.xkcoding.rbac.security.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* <p>
* JWT 响应返回
* </p>
*
* @package: com.xkcoding.rbac.security.vo
* @description: JWT 响应返回
* @author: yangkai.shen
* @date: Created in 2018-12-10 16:01
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtResponse {
/**
* token 字段
*/
private String token;
/**
* token类型
*/
private String tokenType = "Bearer";

public JwtResponse(String token) {
this.token = token;
}
}

+ 151
- 0
spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/UserPrincipal.java View File

@@ -0,0 +1,151 @@
package com.xkcoding.rbac.security.vo;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.xkcoding.rbac.security.common.Consts;
import com.xkcoding.rbac.security.model.Permission;
import com.xkcoding.rbac.security.model.Role;
import com.xkcoding.rbac.security.model.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* <p>
* 自定义User
* </p>
*
* @package: com.xkcoding.rbac.security.vo
* @description: 自定义User
* @author: yangkai.shen
* @date: Created in 2018-12-10 15:09
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserPrincipal implements UserDetails {
/**
* 主键
*/
private Long id;

/**
* 用户名
*/
private String username;

/**
* 密码
*/
@JsonIgnore
private String password;

/**
* 昵称
*/
private String nickname;

/**
* 手机
*/
private String phone;

/**
* 邮箱
*/
private String email;

/**
* 生日
*/
private Long birthday;

/**
* 性别,男-1,女-2
*/
private Integer sex;

/**
* 状态,启用-1,禁用-0
*/
private Integer status;

/**
* 创建时间
*/
private Long createTime;

/**
* 更新时间
*/
private Long updateTime;

/**
* 用户角色列表
*/
private List<String> roles;

/**
* 用户权限列表
*/
private Collection<? extends GrantedAuthority> authorities;

public static UserPrincipal create(User user, List<Role> roles, List<Permission> permissions) {
List<String> roleNames = roles.stream()
.map(Role::getName)
.collect(Collectors.toList());

List<GrantedAuthority> authorities = permissions.stream()
.filter(permission -> StrUtil.isNotBlank(permission.getPermission()))
.map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
.collect(Collectors.toList());

return new UserPrincipal(user.getId(), user.getUsername(), user.getPassword(), user.getNickname(), user.getPhone(), user.getEmail(), user.getBirthday(), user.getSex(), user.getStatus(), user.getCreateTime(), user.getUpdateTime(), roleNames, authorities);
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

@Override
public String getPassword() {
return password;
}

@Override
public String getUsername() {
return username;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return Objects.equals(this.status, Consts.ENABLE);
}
}

Loading…
Cancel
Save