基于ldap实现登录认证

        最近开发的应用需要外协人员实现登录认证,外协人员的密码等信息已经录入到ldap, 需要连接ldap进行登录认证。下面先介绍一下登录的网络旅程图。

一.nginx实现AES加密

nginx请求处理入口(前端请求为json格式)

 location /aes {
        default_type text/html;
        content_by_lua_block{
            local access_filter = require 'resty.aes_auth'
            local r = access_filter.aes_auth()
            ngx.header.content_type = "application/json; charset=UTF-8"
            if r == true then 
	            ngx.say([[{"code":200,"message":"Certification         successful!","data":true,"logCode":null}]])
            else 
	            ngx.say([[{"code":401,"message":"Authentication failed!","data":false,"logCode":null }]])
            end
            ngx.exit(200)
        }

   }

 openresty请求认证接口脚本

local aes = require "resty.aes"
local cjson = require("cjson.safe")
local http = require("resty.http")
local key = "abcdefmJTNn}8Z#2`"
local iv = "1234567890123456"

local _M = {}
function _M.aes_auth()
	ngx.req.read_body()
	local args,err = ngx.req.get_body_data()

	if (not args) or (err) then
			return false
	end
	local arg_json = cjson.decode(args)
	local username = arg_json.username
	local password = arg_json.password
	if (not username) or (not password) then
			return false
	end

	local cript = aes:new(key, nil, aes.cipher(128, "cbc"), {iv=iv, method=nil})
	local pwd = cript:encrypt(password)
	if pwd then
			pwd = ngx.encode_base64(pwd)
	else
			return false
	end

	local httpc = http.new()
	local requestBody = {
			username = username,
			password = pwd
	}
	local json_body = cjson.encode(requestBody)
	local resp,err = httpc:request_uri("http://10.1.1.1:8080", {
			method = "POST",
			path = "/ldap/authUser",
			body = json_body,
			headers = {  ---header参数
					 ["Content-Type"] = "application/json;charset=UTF-8"
			}
	})
	if err then
			return false
	end
	local result = false
	if resp then
			local data = cjson.decode(resp.body).data
			if data then
					result = data
			end
	end

	return result
end

return _M

 二.应用服务调用ldap服务

引入依赖

<!--ldap-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-ldap</artifactId>
	<version>2.3.12.RELEASE</version>
</dependency>
<!--aes对称加密-->
<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcprov-jdk15on</artifactId>
	<version>1.56</version>
</dependency>

AES加密解密工具类,需要注意的是nginx不支持PKCS5Padding填充方式。

package com.xxx.xxx.xxx.util;

import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import lombok.extern.slf4j.Slf4j;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.util.Base64Utils;

/**
 * description:AES对称加密工具类
 *
 * @author: lgq
 * @create: 2024-01-26 10:03
 */
@Slf4j
public class AESUtil {
    /**
     * 日志相关
     */
    /**
     * 编码
     */
    private static final String ENCODING = "UTF-8";
    /**
     * 算法定义
     */
    private static final String AES_ALGORITHM = "AES";
    /**
     * 指定填充方式
     */
    private static final String CIPHER_PADDING = "AES/ECB/PKCS5Padding";
    //必须使用PKCS7Padding,因为nginx不支持PKCS5Padding填充方式
    private static final String CIPHER_CBC_PADDING = "AES/CBC/PKCS7Padding";
    /**
     * 偏移量(CBC中使用,增强加密算法强度)
     */
    private static final String IV_SEED = "1234567890123456";
   
    private static final String RANDOM_SECRET = "abcefmJTNn}8Z#2`";

    static {
        // 指定使用bouncycastle包来加密, 引入目的就是为了支持AES/CBC/PKCS7Padding
        Security.addProvider(new BouncyCastleProvider());
    }


    public static String getRandomSecret() {
        return RANDOM_SECRET;
    }

    /**
     * AES加密
     *
     * @param content 待加密内容
     * @param aesKey  密码
     * @return
     */
    public static String encrypt(String content, String aesKey) {
        if (StringUtils.isBlank(content)) {
            log.info("AES encrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置加密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
                //选择加密
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
                //根据待加密内容生成字节数组
                byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
                //返回base64字符串
                return Base64Utils.encodeToString(encrypted);
            } catch (Exception e) {
                log.info("AES encrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }

        } else {
            log.info("AES encrypt: the aesKey is null or error!");
            return null;
        }
    }

    /**
     * 解密
     *
     * @param content 待解密内容
     * @param aesKey  密码
     * @return
     */
    public static String decrypt(String content, String aesKey) {
        if (StringUtils.isBlank(content)) {
            log.info("AES decrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置解密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
                //选择解密
                cipher.init(Cipher.DECRYPT_MODE, skeySpec);

                //先进行Base64解码
                byte[] decodeBase64 = Base64Utils.decodeFromString(content);

                //根据待解密内容进行解密
                byte[] decrypted = cipher.doFinal(decodeBase64);
                //将字节数组转成字符串
                return new String(decrypted, ENCODING);
            } catch (Exception e) {
                log.info("AES decrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }

        } else {
            log.info("AES decrypt: the aesKey is null or error!");
            return null;
        }
    }

    /**
     * AES_CBC加密
     *
     * @param content 待加密内容
     * @param aesKey  密码
     * @return
     */
    public static String encryptCBC(String content, String aesKey) {
        if (StringUtils.isBlank(content)) {
            log.info("AES_CBC encrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置加密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
                //偏移
                IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));
                //选择加密
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);//, iv
                //根据待加密内容生成字节数组
                byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
                //返回base64字符串
                return Base64Utils.encodeToString(encrypted);
            } catch (Exception e) {
                log.info("AES_CBC encrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }

        } else {
            log.info("AES_CBC encrypt: the aesKey is null or error!");
            return null;
        }
    }

    /**
     * AES_CBC解密
     *
     * @param content 待解密内容
     * @param aesKey  密码
     * @return
     */
    public static String decryptCBC(String content, String aesKey) {
        if (StringUtils.isBlank(content)) {
            log.info("AES_CBC decrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置解密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                //偏移
                IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
                //选择解密
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

                //先进行Base64解码
                byte[] decodeBase64 = Base64Utils.decodeFromString(content);

                //根据待解密内容进行解密
                byte[] decrypted = cipher.doFinal(decodeBase64);
                //将字节数组转成字符串
                return new String(decrypted, ENCODING);
            } catch (Exception e) {
                log.info("AES_CBC decrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }

        } else {
            log.info("AES_CBC decrypt: the aesKey is null or error!");
            return null;
        }
    }

}

ladp配置类

package com.xxx.xxx.xxx.config;

import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;

/**
 * description:LdapConfig
 *
 * @author: lgq
 * @create: 2024-01-25 10:34
 */
@Configuration
public class LdapConfig {
    @Resource
    private LdapProperties ldapProperties;

    @Bean
    public LdapTemplate ldapTemplate() {
        LdapContextSource contextSource = new LdapContextSource();
        contextSource.setUrl(ldapProperties.getUrls());
        contextSource.setBase(ldapProperties.getBase());
        contextSource.setUserDn(ldapProperties.getUsername());
        contextSource.setPassword(ldapProperties.getPassword());
        contextSource.afterPropertiesSet();

        LdapTemplate ldapTemplate = new LdapTemplate(contextSource);
        ldapTemplate.setIgnorePartialResultException(true);
        ldapTemplate.setDefaultTimeLimit(1000);
        ldapTemplate.setDefaultCountLimit(100);

        return ldapTemplate;
    }


}


package com.xxx.xxx.xxx.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * description:ldapProperties
 *
 * @author: lgq
 * @create: 2024-01-25 18:13
 */
@Data
@ConfigurationProperties(prefix = "spring.ldap")
public class LdapProperties {
    /**
     * ldap服务地址
     */
    private String urls;

    /**
     * 用户账号
     */
    private String username;

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

    /**
     * base路径
     */
    private String base;
}


yml文件配置:

spring:
  profiles: prod
  application:
    name: service-xxx
  ldap:
    urls: "ldap://10.1.1.1:389"
    password: "xxxxxxxx"
    username: "cn=xxx.LDAP,ou=xxx,ou=xxx,dc=xxx,dc=xxx"
    base: "dc=xxx,dc=xxx"

认证服务类

package com.xxx.xxx.xxx.service.impl;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

import javax.annotation.Resource;

import com.xxx.xxx.xxx.service.LdapService;
import com.xxx.xxx.xxx.util.AESUtil;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.stereotype.Service;

/**
 * description:LdapServiceImpl
 *
 * @author: lgq
 * @create: 2024-01-26 09:26
 */
@Service
@Slf4j
public class LdapServiceImpl implements LdapService {
    @Resource
    private LdapTemplate ldapTemplate;

    /**
     * 验证登录用户的账号密码是否正确
     * @param username
     * @param password
     * @return
     */
    @Override
    public boolean authLoginUser(String username, String password) {
        if (ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(password)) {
            return false;
        }

        /**
         * aes对password进行解密
         */
        String content = AESUtil.decryptCBC(password, AESUtil.getRandomSecret());
        if (ObjectUtils.isEmpty(content)) {
            return false;
        }

        String baseDn = "";
        String filter = "sAMAccountName=" + username;
        boolean result = false;
        try {
            result = ldapTemplate.authenticate(baseDn, filter, content);
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        } catch (Error er) {
            log.error(er.getMessage(), er);
        }
        return result;
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/358547.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode常见错误

1 runtime error: load of null pointer of type ‘std::_Bit_type‘ (aka ‘unsigned long‘) (stl_bvector&#xff09; 力扣&#xff1a;runtime error: load of null pointer of type ‘std::_Bit_type‘ (aka ‘unsigned long‘) (stl_bvector&#xff09;_runtime error…

GitLab 中国发行版如何设置镜像拉取策略?

最近在用极狐GitLab&#xff08;极狐GitLab 可以理解为 GitLab 在中国的发行版&#xff09; CI/CD 的时候遇到一个问题&#xff1a;CI/CD 中有一个 stage 需要拉取 dockerhub 上的镜像&#xff0c;但是由于 dockerhub 在国内的访问不是很顺畅&#xff0c;经常发生 timeout 的情…

方法阻塞的解决方案之一

1、简单使用 一个h一个cpp文件 #pragma once #include <iostream> #include <thread> #include <atomic> #include <chrono> #include <string>class Person {public:struct dog {std::string name;int age;};public:void a(std::atomic<bo…

链表——超详细

一、无头单向非循环链表 1.结构&#xff08;两个部分&#xff09;&#xff1a; typedef int SLTDataType; typedef struct SListNode {SLTDataType data;//数据域struct SListNode* next;//指针域 }SLNode; 它只有一个数字域和一个指针域&#xff0c;里面数据域就是所存放的…

一些著名的软件都用什么语言编写?

1、操作系统 Microsoft Windows &#xff1a;汇编 -> C -> C 备注&#xff1a;曾经在智能手机的操作系统&#xff08;Windows Mobile&#xff09;考虑掺点C#写的程序&#xff0c;比如软键盘&#xff0c;结果因为写出来的程序太慢&#xff0c;实在无法和别的模块合并&…

宠物空气净化器适合养猫家庭吗?猫用空气净化器品牌推荐!

养宠物的家庭都了解到&#xff0c;宠物掉毛是一个令人头痛的问题。即使我们及时清理地面&#xff0c;也很难跟上宠物掉毛的速度。飘散的毛发不仅让家里显得不整洁&#xff0c;还可能对家人的呼吸健康产生影响&#xff0c;甚至引起过敏反应。此外&#xff0c;猫咪每天上厕所&…

vue3之echarts3D环柱图-间隔版

vue3之echarts3D环柱图-间隔版 效果&#xff1a; 版本 "echarts": "^5.4.1", "echarts-gl": "^2.0.9" 核心代码&#xff1a; <template><div class"content"><div ref"eCharts" class"c…

零基础自学C语言|内存函数

&#x1f50d;memcpy的使用与模拟实现 格式如下&#xff1a; void* memcpy(void* destination, const void* source, size_t num); 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。这个函数在遇到\0的时候并不会停下来。如果source和de…

Flask 入门1:一个简单的 Web 程序

1. 关于 Flask Flask诞生于2010年&#xff0c; Armin Ronacher的一个愚人节玩笑。不过现在已经是一个用python语言基于Werkzeug工具箱编写的轻量级web开发框架&#xff0c;它主要面向需求简单&#xff0c;项目周期短的小应用。 Flask本身相当于一个内核&#xff0c;其他几乎所…

基于SpringBoot实现的AI智能大数据医疗诊断平台

系统介绍 系统演示 微信关注视频号&#xff1a;【全栈小白】&#xff0c;查看演示视频 基于SpringBoot实现的AI智能大数据医疗诊断平台&#xff0c;主要包含六个大模块&#xff1a;系统管理、居民医保信息、药物信息管理、居民健康信息、居民就诊信息和我的预约信息。项目启…

Python中的递归函数是什么

Python 递归函数 递归的特性&#xff1a; 1.调用自身函数 2.有一个结束条件 3.递归效率不高&#xff0c;可能会导致栈溢出(函数调用是通过栈这种数据结构实现的&#xff0c;每进入一个函数调用&#xff0c;栈就会增加一层栈帧&#xff0c;函数每返回&#xff0c;栈就会减少…

C++11—— lambda表达式与包装器

C11—— lambda表达式与包装器 文章目录 C11—— lambda表达式与包装器一、 lambda表达式lambda表达式产生的意义lambda表达式语法函数对象与lambda表达式 二、 包装器functionfunction产生的意义function的用法function使用的例子 bind调整参数顺序固定绑定参数 一、 lambda表…

解锁Web3:数字未来的大门

随着科技的不断推进&#xff0c;我们正站在数字时代的新门槛上。Web3&#xff0c;作为互联网的下一个演进阶段&#xff0c;正在逐渐揭开数字未来的面纱。本文将深入探讨Web3的本质、对社会的影响以及在数字时代中所扮演的关键角色。 什么是Web3&#xff1f; Web3是互联网发展的…

git仓库批量备份

git的mirror参数 在git中&#xff0c;--mirror是一个用于克隆和推送操作的参数。它用于创建一个镜像仓库&#xff0c;包含了源仓库的所有分支、标签和提交历史记录。 当使用git clone --mirror <source-repo>命令时&#xff0c;会创建一个完全相同的镜像仓库&#xff0…

ICMP——网际控制报文协议

目录 1.1 网际控制报文协议 ICMP 1.2 ICMP 报文的格式 1.2.1 ICMP 报文的种类 ICMP 差错报告报文 ICMP 询问报文 1.3 ICMP 的应用 1.4 ICMP抓包 1.4.1 ICMP请求包&#xff08;request&#xff09; 1.4.2 ICMP应答包&#xff08;reply&#xff09; 1.1 网际控制报文协议…

WPOpenSocial实现WordPress的QQ登录

个人建站不可避免的需要自己搭建用户数据库的问题&#xff0c;可用户却往往因为注册繁琐而放弃浏览您的网站&#xff0c;由此可见&#xff0c;一个社交账号一键登录方式尤为重要。选择适合您网站需求的社交插件&#xff0c;可以提升用户互动&#xff0c;增加社交分享&#xff0…

深度解析单片机:历史、发展与您关心的问题

什么是单片机&#xff1f; 定义&#xff1a;单片机是一种集成了中央处理器&#xff08;CPU&#xff09;、内存和外设功能的微型计算机系统。与传统计算机相比&#xff0c;单片机通常集成在一个芯片上&#xff0c;用于控制特定的应用。#单片机# 特点&#xff1a; 封装紧凑&…

HarmonyOS NEXT 星河版项目案例

参考代码&#xff1a;HeimaHealthy: 鸿蒙项目案例练习 (gitee.com) 1.欢迎页面 Entry Component struct WelcomePage {State message: string Hello Worldbuild() {Column({space: 10}) {Row() {// 1.中央slogonImage($r(app.media.home_slogan)).width(260)}.layoutWeight(…

机器学习模型预测贷款审批

机器学习模型预测贷款审批 作者&#xff1a;i阿极 作者简介&#xff1a;数据分析领域优质创作者、多项比赛获奖者&#xff1a;博主个人首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x1f44d;收藏&#x1f4c1;评论&…