English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

سجلات عملية التحقق المخصص في Spring Security

Spring Security使用分类:

如何使用Spring Security,相信百度过的人都知道,总共有四种用法,从简单到复杂为:

1、不使用数据库,所有数据都写在配置文件中,这也是官方文档中的demo;

2、使用数据库,根据Spring Security默认实现代码设计数据库,也就是说数据库已经固定了,这种方法不够灵活,而且那个数据库设计得很简陋,实用性差;

3、Spring Security与Acegi不同,它不能修改默认filter了,但支持插入filter,因此我们可以插入自己的filter来灵活使用;

4.استخدام الطرق القوية، تعديل الشيفرة المصدرية، ما ذكرناه من تعديل filter الافتراضي هو مجرد تعديل ملف التكوين لت替代 filter، هذا التعديل يغير الشيفرة الداخلية مباشرة، ولكن هذا غير متوافق مع مبادئ تصميم الـ OO، وهو غير عملي وغير مستخدم.

هذا المقال يقدم محتوى حول تحقق الدخول المخصص لـ spring security، ويساهم في مشاركة المعرفة للاستفادة من الجميع، لن نتحدث كثيرًا، دعونا نرى الشرح التفصيلي الآن.

1.ملخص

1.1.مقدمة

spring security هي إطار أمان يعتمد على Spring AOP وServlet Filters لإدارة التحقق والصلاحيات.

1.2.مسار التحقق المخصص لـ spring security

1) عملية التحقق

إنشاء Token التحقق غير الناجح                 

 ↑(الحصول على معلومات)  (توزيع provider بناءً على Token التحقق)     
 AuthenticationFilter -> AuthenticationManager -> AuthenticationProvider
        ↓(التحقق)
       UserDetails (عادة يتم الحصول عليها من قاعدة البيانات)
        ↓(من خلال)
        إنشاء Token التحقق الناجح
         ↓(يخزن)
        SecurityContextHolder

2) أضف AuthenticationFilter إلى سلسلة التصفية الخاصة بالأمان (في ملف تكوين الخادم المصدر)، مثل:

http.addFilterBefore(AuthenticationFilter, AbstractPreAuthenticatedProcessingFilter.class)

أو:

http.addFilterAfter(AuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

2.بمثابة مثال على الدخول عبر رسائل SMS باستخدام الرقم الهاتف

2.1.بيئة التطوير

  • SpringBoot
  • Spring security
  • Redis

2.2.تحليل الشيفرة الأساسية

2.2.1.مسار التحقق المخصص للدخول

2.2.1.1.Token التحقق المخصص للدخول

/**
 * Token للدخول عبر الهاتف
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationToken extends AbstractAuthenticationToken {
 private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationToken.class.getName());
 private final Object principal;
 public MobileLoginAuthenticationToken(String mobile) {
 super(null);
 this.principal = mobile;
 this.setAuthenticated(false);
 logger.info("MobileLoginAuthenticationToken setAuthenticated ->false loading ...");
 }
 public MobileLoginAuthenticationToken(Object principal,
      Collection<? extends GrantedAuthority> authorities) {
 super(authorities);
 this.principal = principal;
 // يجب استخدام super، لأننا نعيد تعريف
 super.setAuthenticated(true);
 logger.info("MobileLoginAuthenticationToken setAuthenticated ->true loading ...");
 }
 @Override
 public void setAuthenticated(boolean authenticated) {
 if (authenticated) {
  throw new IllegalArgumentException;
   "لا يمكن تعيين هذا التoken كموثوق - استخدم بناءً على المعين الذي يأخذ قائمة GrantedAuthority بدلاً من ذلك";
 }
 super.setAuthenticated(false);
 }
 @Override
 public Object getCredentials() {
 return null;
 }
 @Override
 public Object getPrincipal() {
 return this.principal;
 }
 @Override
 public void eraseCredentials() {
 super.eraseCredentials();
 }
}

ملاحظة:

setAuthenticated():判断是否已认证

  • 在过滤器时,会生成一个未认证的AuthenticationToken,此时调用的是自定义token的setAuthenticated(),此时设置为false -> 未认证
  • 在提供者时,会生成一个已认证的AuthenticationToken,此时调用的是父类的setAuthenticated(),此时设置为true -> 已认证

2.2.1.1.自定义认证登录过滤器

/**
 * 手机短信登录过滤器
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
 private boolean postOnly = true;
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationFilter.class.getName());
 @Getter
 @Setter
 private String mobileParameterName;
 public MobileLoginAuthenticationFilter(String mobileLoginUrl, String mobileParameterName,
      String httpMethod) {
 super(new AntPathRequestMatcher(mobileLoginUrl, httpMethod));
 this.mobileParameterName = mobileParameterName;
 logger.info("MobileLoginAuthenticationFilter التحميل... ");
 }
 @Override
 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
 if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
  throw new AuthenticationServiceException("طريقة التحقق غير مدعومة: " + request.getMethod());
 }
 // الحصول على رقم الهاتف
 String mobile = obtainMobile(request);
 // تجميع التoken
 MobileLoginAuthenticationToken authRequest = new MobileLoginAuthenticationToken(mobile);
 //سمح للفرز الفرعي بإعداد الخاصية "details"
 setDetails(request, authRequest);
 return this.getAuthenticationManager().authenticate(authRequest);
 }
 /**
 * إعداد تفاصيل التحقق من الهوية
 */
 private void setDetails(HttpServletRequest request, MobileLoginAuthenticationToken authRequest) {
 authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
 }
 /**
 * الحصول على رقم الهاتف
 */
 private String obtainMobile(HttpServletRequest request) {
 return request.getParameter(mobileParameterName);
 }
 public void setPostOnly(boolean postOnly) {
 this.postOnly = postOnly;
 }
}

ملاحظة:attemptAuthentication()方法:

  • 过滤指定的url、httpMethod
  • 获取所需请求参数数据封装生成一个未认证的AuthenticationToken
  • 传递给AuthenticationManager认证

2.2.1.1.自定义认证登录提供者

/**
 * 手机短信登录认证提供者
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationProvider implements AuthenticationProvider {
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationProvider.class.getName());
 @Getter
 @Setter
 private UserDetailsService customUserDetailsService;
 public MobileLoginAuthenticationProvider() {
 logger.info("MobileLoginAuthenticationProvider loading ...");
 }
 /**
 * 认证
 */
 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 //获取过滤器封装的token信息
 MobileLoginAuthenticationToken authenticationToken = (MobileLoginAuthenticationToken) authentication;
 //获取用户信息(数据库认证)
 UserDetails userDetails = customUserDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
 //不通过
 if (userDetails == null) {
  throw new InternalAuthenticationServiceException("Unable to obtain user information");
 }
 //通过
 MobileLoginAuthenticationToken authenticationResult = new MobileLoginAuthenticationToken(userDetails, userDetails.getAuthorities());
 authenticationResult.setDetails(authenticationToken.getDetails());
 return authenticationResult;
 }
 /**
 * 根据token类型,来判断使用哪个Provider
 */
 @Override
 public boolean supports(Class<?> authentication) {
 return MobileLoginAuthenticationToken.class.isAssignableFrom(authentication);
 }
}

ملاحظة:authenticate()方法

  • 获取过滤器封装的token信息
  • 调取UserDetailsService获取用户信息(数据库认证)->判断通过与否
  • 通过则封装一个新的AuthenticationToken,并返回

2.2.1.1.自定义认证登录认证配置

@Configuration(SpringBeanNameConstant.DEFAULT_CUSTOM_MOBILE_LOGIN_AUTHENTICATION_SECURITY_CONFIG_BN)
public class MobileLoginAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationSecurityConfig.class.getName());
 @Value("${login.mobile.url}")
 private String defaultMobileLoginUrl;
 @Value("${login.mobile.parameter}")
 private String defaultMobileLoginParameter;
 @Value("${login.mobile.httpMethod}")
 private String defaultMobileLoginHttpMethod;
 @Autowired
 private CustomYmlConfig customYmlConfig;
 @Autowired
 private UserDetailsService customUserDetailsService;
 @Autowired
 private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
 @Autowired
 private AuthenticationFailureHandler customAuthenticationFailureHandler;
 public MobileLoginAuthenticationSecurityConfig() {
 logger.info("MobileLoginAuthenticationSecurityConfig loading ...");
 }
 @Override
 public void configure(HttpSecurity http) throws Exception {
 MobilePOJO mobile = customYmlConfig.getLogins().getMobile();
 String url = mobile.getUrl();
 String parameter = mobile.getParameter().getMobile();
 String httpMethod = mobile.getHttpMethod();
 MobileLoginAuthenticationFilter mobileLoginAuthenticationFilter = new MobileLoginAuthenticationFilter(StringUtils.isBlank(url) ? defaultMobileLoginUrl : url,
  StringUtils.isBlank(parameter) ? defaultMobileLoginUrl : parameter, StringUtils.isBlank(httpMethod) ? defaultMobileLoginHttpMethod : httpMethod); mobileLoginAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); mobileLoginAuthenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler); mobileLoginAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
 MobileLoginAuthenticationProvider mobileLoginAuthenticationProvider = new MobileLoginAuthenticationProvider(); mobileLoginAuthenticationProvider.setCustomUserDetailsService(customUserDetailsService);
 http.authenticationProvider(mobileLoginAuthenticationProvider)
  .addFilterAfter(mobileLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
 }
}

ملاحظة:configure()方法

实例化AuthenticationFilter和AuthenticationProvider

将AuthenticationFilter和AuthenticationProvider添加到spring security中。

2.2.2.基于redis的自定义验证码校验

2.2.2.1.基于redis的自定义验证码过滤器

/**
 * 验证码过滤器
 *
 * @author : CatalpaFlat
 */
@Component(SpringBeanNameConstant.DEFAULT_VALIDATE_CODE_FILTER_BN)
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
 private static final Logger logger = LoggerFactory.getLogger(ValidateCodeFilter.class.getName());
 @Autowired
 private CustomYmlConfig customYmlConfig;
 @Autowired
 private RedisTemplate<Object, Object> redisTemplate;
 /**
  * 工具类,用于验证请求url与配置的url是否匹配
  */
 private AntPathMatcher pathMatcher = new AntPathMatcher();
 public ValidateCodeFilter() {
  logger.info("Loading ValidateCodeFilter...");
 }
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
         FilterChain filterChain) throws ServletException, IOException {
  String url = customYmlConfig.getLogins().getMobile().getUrl();
  if (pathMatcher.match(url, request.getRequestURI())) {
   String deviceId = request.getHeader("deviceId");
   if (StringUtils.isBlank(deviceId)) {
    throw new CustomException(HttpStatus.NOT_ACCEPTABLE.value(), "Not deviceId in the head of the request");
   }
   String codeParamName = customYmlConfig.getLogins().getMobile().getParameter().getCode();
   String code = request.getParameter(codeParamName);
   if (StringUtils.isBlank(code)) {
    throw new CustomException(HttpStatus.NOT_ACCEPTABLE.value(), "Not code in the parameters of the request");
   }
   String key = SystemConstant.DEFAULT_MOBILE_KEY_PIX + deviceId;
   SmsCodePO smsCodePo = (SmsCodePO) redisTemplate.opsForValue().get(key);
   if (smsCodePo.isExpried()){
    throw new CustomException(HttpStatus.BAD_REQUEST.value(), "The verification code has expired");
   }
   String smsCode = smsCodePo.getCode();
   if (StringUtils.isBlank(smsCode)) {
    throw new CustomException(HttpStatus.BAD_REQUEST.value(), "Verification code does not exist");
   }
   if (StringUtils.equals(code, smsCode)) {
    redisTemplate.delete(key);
    //let it go
    filterChain.doFilter(request, response);
   } else {
    throw new CustomException(HttpStatus.BAD_REQUEST.value(), "رمز التحقق غير صحيح");
   }
  } else {
   //let it go
   filterChain.doFilter(request, response);
  }
 }
}

ملاحظة:doFilterInternal()

تحقق مسبق التحقق من رمز التحقق المخصص

2.2.2.2. إضافة مسبق التحقق من رمز التحقق المخصص إلى سلسلة مرشحات spring security

http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class)

ملاحظة:إضافة إلى مسبق معالجة التحقق

3. تأثير الاختبار

في النهاية، سنقدم عنوان عنوان المصدر:https://gitee.com/CatalpaFlat/springSecurity.git  (تنزيل محلي)

النهاية

هذا هو نهاية محتوى هذا المقال، نأمل أن يكون محتوى هذا المقال له قيمة مرجعية لتعلمكم أو عملكم، إذا كان لديكم أي أسئلة، يمكنكم ترك تعليقات للتفاعل، شكرًا لدعمكم لتعليمات الصراخ.

بيان: محتوى هذا المقال تم جمعه من الإنترنت، ملكية المحتوى لصاحب الحقوق، تم جمع المحتوى من قبل المستخدمين على الإنترنت بشكل تلقائي، هذا الموقع لا يمتلك حقوق الملكية، لم يتم تعديل المحتوى بشكل يدوي، ولا يتحمل أي مسؤولية قانونية متعلقة بذلك. إذا كنت قد وجدت محتوى يشتبه في انتهاك حقوق النسخ، فنرجو منك إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (عند إرسال البريد الإلكتروني، يرجى استبدال # بـ @) للإبلاغ، وقدم الدليل على الدليل، إذا تم التحقق من ذلك، سيتم حذف المحتوى المزعوم فورًا.

سيكون لك حبك