一、自定義校驗器
jdk 和 hibernate-validator 定義了很多校驗的注解,但是畢竟這些都是提前定義好的,有時候并不能滿足我們的校驗需求,是以我們可以自定義校驗注解和校驗規則
1、jdk所提供的校驗規則
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL41keOFTWU9EeRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2UzN0EzNyQTMzEDNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
2、hibernate-validator所提供的校驗規則
校驗注解
/**
* @Description : 自定義生日檢驗注解
* @Date: 2021/4/10 00:23
* @Author : tiankun
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Constraint(validatedBy = BirthdayValidatorClass.class)
public @interface BirthdayValidator {
String[] value() default {};
String message() default "日期是不能在 2000-01-01 之前";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
校驗器
/**
* @Description : 生日檢驗器
* @Date: 2021/4/10 00:25
* @Author : tiankun
*/
public class BirthdayValidatorClass implements ConstraintValidator<BirthdayValidator, Date> {
@SneakyThrows
@Override
public boolean isValid(Date value, ConstraintValidatorContext context) {
/*
校驗日期是否是在 2000-01-01 之前
*/
Date date = new SimpleDateFormat("YYYY-MM-dd").parse("2000-01-01");
return value.after(date);
}
}
測試使用
實體類
/**
* @Description : 使用者實體類
* @Date: 2021/4/10 00:06
* @Author : tiankun
*/
@Data
public class User {
private Integer id;
private String name;
@BirthdayValidator
private Date birthday;
}
Controller
@PostMapping("/test")
public String test( @Validated @RequestBody User user){
System.out.println(user);
return "111";
}
全局處理器
/**
* @Description : 異常處理類
* @Date: 2021/4/10 08:56
* @Author : tiankun
*/
@RestControllerAdvice
@Slf4j
public class GlobolExceptionAddvice {
/**
* 校驗異常處理 MethodArgumentNotValidException
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public String handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
BindingResult bindingResult = e.getBindingResult();
FieldError fieldError = bindingResult.getFieldError();
log.info("出現了 MethodArgumentNotValidException 異常:"+fieldError.getDefaultMessage());
return null;
}
}
利用postman發送請求
控制台
二、在非Spring環境通過擷取bean
封裝一個SpringUtils工具類,可以在非Spring環境中進行調用即可
/**
* Spring工具類
*/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
// 擷取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
// 通過name擷取Bean
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
// 通過class擷取Bean
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
// 通過name,以及Clazz傳回指定的Bean
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
三、注解 @EnableWebSecurity
https://blog.csdn.net/andy_zhang2007/article/details/90023901
四、根據URI實作動态權限
參考:https://blog.csdn.net/hyk521/article/details/107215268/
mall 裡面的實作:
正常的SpringSecurity的流程就不贅述(場景實體類,建立UserDetails,實作UserDetail接口,如果有利于JWT發送token ,以及JWT的過濾器等等)
直接到重點:
UserDetails
/**
* SpringSecurity需要的使用者詳情
* Created by macro on 2018/4/26.
*/
public class AdminUserDetails implements UserDetails {
// 使用者
private UmsAdmin umsAdmin;
// 資源權限清單
private List<UmsResource> resourceList;
public AdminUserDetails(UmsAdmin umsAdmin,List<UmsResource> resourceList) {
this.umsAdmin = umsAdmin;
this.resourceList = resourceList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//傳回目前使用者的角色
return resourceList.stream()
.map(role ->new SimpleGrantedAuthority(role.getId()+":"+role.getName()))
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return umsAdmin.getPassword();
}
@Override
public String getUsername() {
return umsAdmin.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return umsAdmin.getStatus().equals(1);
}
}
自定義加載資源權限的Service 接口以及實作
/**
* 動态權限相關業務類
* Created by macro on 2020/2/7.
*/
public interface DynamicSecurityService {
/**
* 加載資源ANT通配符和資源對應MAP
*/
Map<String, ConfigAttribute> loadDataSource();
}
/**
* mall-security子產品相關配置
* Created by macro on 2019/11/9.
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MallSecurityConfig extends SecurityConfig {
@Autowired
private UmsAdminService adminService;
@Autowired
private UmsResourceService resourceService;
@Bean
public UserDetailsService userDetailsService() {
//擷取登入使用者資訊
return username -> adminService.loadUserByUsername(username);
}
/**
* 擷取所有的資源權限
*/
@Bean
public DynamicSecurityService dynamicSecurityService() {
return new DynamicSecurityService() {
@Override
public Map<String, ConfigAttribute> loadDataSource() {
Map<String, ConfigAttribute> map = new ConcurrentHashMap<>();
List<UmsResource> resourceList = resourceService.listAll();
for (UmsResource resource : resourceList) {
map.put(resource.getUrl(), new org.springframework.security.access.SecurityConfig(resource.getId() + ":" + resource.getName()));
}
return map;
}
};
}
}
自定義 FilterInvocationSecurityMetadataSource
/**
* 動态權限資料源,用于擷取動态權限規則
* Created by macro on 2020/2/7.
*/
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
// 所有的資源集合
private static Map<String, ConfigAttribute> configAttributeMap = null;
@Autowired
private DynamicSecurityService dynamicSecurityService;
/**
* 擷取所有的資源資訊
* <p>
* 在getAttributes()方法内不可直接使用dynamicSecurityService,因為Security先于Spring加載,此時還沒有注入,會報空指針異常
* 被@PostConstruct修飾的方法會在伺服器加載Servlet的時候運作,并且隻會被伺服器執行一次,依賴注入初始化後會自動執行該方法
*/
@PostConstruct
public void loadDataSource() {
configAttributeMap = dynamicSecurityService.loadDataSource();
}
/**
* 請求目前的資源集合
*/
public void clearDataSource() {
configAttributeMap.clear();
configAttributeMap = null;
}
/**
* 根據目前請求的url擷取操作權限的屬性 eg: 通過 /product/list ==> 5:商品管理
* @param o (FilterInvocation)o 用于擷取目前請求的URL
* @return 傳回目前URL對應的資源
* @throws IllegalArgumentException
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
if (configAttributeMap == null) {
this.loadDataSource();
}
List<ConfigAttribute> configAttributes = new ArrayList<>();
//擷取目前通路的路徑
String url = ((FilterInvocation) o).getRequestUrl();
String path = URLUtil.getPath(url);
PathMatcher pathMatcher = new AntPathMatcher();
Iterator<String> iterator = configAttributeMap.keySet().iterator();
//擷取通路該路徑所需資源
while (iterator.hasNext()) {
String pattern = iterator.next();
if (pathMatcher.match(pattern, path)) {
configAttributes.add(configAttributeMap.get(pattern));
}
}
// 未設定操作請求權限,傳回空集合
return configAttributes;
}
/**
* 傳回所有定義好的權限資源
* Spring Security在啟動時會校驗相關配置是否正确,如果不需要校驗,直接傳回null
*/
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
/**
* 是否支援校驗
*/
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
configAttributeMap 裡面的資料
請求一個路徑所傳回的 資源權限辨別
自定義 AccessDecisionManager
/**
* 動态權限決策管理器,用于判斷使用者是否有通路權限
* Created by macro on 2020/2/7.
*/
public class DynamicAccessDecisionManager implements AccessDecisionManager {
/**
* 判斷目前登入的使用者是否具備目前請求URL所需要的資源權限
* 如果不具備,就抛出 AccessDeniedException 異常,否則不做任何事即可
* @param authentication 目前登入用的資訊
* @param object 可以擷取目前請求對象
* @param configAttributes DynamicSecurityMetadataSource 中的 getAttributes() 方法的傳回值,即目前請求URL所需的角色
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
// 當接口未被配置資源時直接放行
if (CollUtil.isEmpty(configAttributes)) {
return;
}
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
//将通路所需資源或使用者擁有資源進行比對
String needAuthority = configAttribute.getAttribute();
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) { // authentication.getAuthorities() 擷取目前使用者擁有所有的資源權限
if (needAuthority.trim().equals(grantedAuthority.getAuthority())) {
return;
}
}
}
// 如果沒有比對的就報沒有權限通路
throw new AccessDeniedException("抱歉,您沒有通路權限");
}
/**
* 是否支援校驗
*/
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
/**
* 是否支援校驗
*/
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
配置動态權限過濾器,用于實作基于路徑的動态權限過濾
上面的部落格裡面直接将上面的實作配置到了配置類中
mall 采用的是利用 Filter的實作
/**
* 動态權限過濾器,用于實作基于路徑的動态權限過濾
* Created by macro on 2020/2/7.
*/
public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter {
@Autowired
private DynamicSecurityMetadataSource dynamicSecurityMetadataSource;
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Autowired
public void setMyAccessDecisionManager(DynamicAccessDecisionManager dynamicAccessDecisionManager) {
super.setAccessDecisionManager(dynamicAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
// OPTIONS請求直接放行
if(request.getMethod().equals(HttpMethod.OPTIONS.toString())){
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
// 白名單請求直接放行
PathMatcher pathMatcher = new AntPathMatcher();
for (String path : ignoreUrlsConfig.getUrls()) {
if(pathMatcher.match(path,request.getRequestURI())){
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
}
// 此處會調用AccessDecisionManager中的decide方法進行鑒權操作
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
// 安全對象調用完成後,完成AbstractSecurityInterceptor的工作。
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return dynamicSecurityMetadataSource;
}
}
上面代碼 IgnoreUrlsConfig 白名單
/**
* 用于配置白名單資源路徑
* Created by macro on 2018/11/5.
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {
private List<String> urls = new ArrayList<>();
}
secure:
ignored:
urls: #安全路徑白名單
- /swagger-ui.html
- /swagger-resources/**
- /swagger/**
- /**/v2/api-docs
- /**/*.js
- /**/*.css
- /**/*.png
- /**/*.ico
- /webjars/springfox-swagger-ui/**
- /actuator/**
- /druid/**
- /admin/login
- /admin/register
- /admin/info
- /admin/logout
- /minio/upload
這種不同于我們之前慣用的 @PreAuthorize()
方式 ,可以直接通過資料庫的配置和URI實作統一的權限認證,統一的配置确實減少了代碼的備援,但是它總的來說沒有 @PreAuthorize() 更加細粒度,但是對于實作基本的權限功能綽綽有餘,可能以前使用 @PreAuthorize() 方式用的比較多,反而感覺這種方式有點怪怪的。
五、擷取插入資料自動生成的id的值的三種方法
可以參考之前我寫的部落格:https://blog.csdn.net/qq_41965731/article/details/111937197
六、@RequestParam 的 defaultValue屬性
我們常用@RequestParam 進行前端傳來的屬性與參數構成映射關系,但是往往會忽略掉它的 defaultValue 屬性,
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
// 要求所映射的資料是否必需有
boolean required() default true;
// 預設值 ,如果傳輸參數沒有比對上則使用預設值,若比對上則使用傳輸過來的内容
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}
通過 @RequestParam 可以實作一些如果使用者沒有給我們傳遞資料,我們就采取預設值的情況,比如當分頁查詢的時候,如果前台傳入分頁的起始頁和每頁顯示的記錄數則采取前台轉過來的資料,如果沒有我們就可以采用預設值
public CommonResult<CommonPage<PmsProduct>> getList(PmsProductQueryParam productQueryParam,
@RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize,
@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum) {
List<PmsProduct> productList = productService.list(productQueryParam, pageSize, pageNum);