基于ldap实现登录认证

本文介绍了如何在使用OpenResty和SpringBoot的应用中,通过AES加密处理用户密码并与LDAP进行安全登录认证的过程,包括Nginx的AES加密入口、OpenResty的认证接口以及SpringBoot的LDAP配置和认证服务实现。
摘要由CSDN通过智能技术生成

        最近开发的应用需要外协人员实现登录认证,外协人员的密码等信息已经录入到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;
    }

}

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LDAP (Lightweight Directory Access Protocol) 是一种基于 TCP/IP 协议的轻量级目录访问协议,通常用于企业级应用程序中的用户身份验证和授权。Spring Boot 提供了对 LDAP 认证的支持,可以轻松地将其集成到应用程序中。 下面是一个基本的 LDAP 认证示例: 1. 添加依赖 在 pom.xml 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-ldap</artifactId> </dependency> ``` 2. 配置 application.yml 在 application.yml 文件中添加以下配置: ```yaml spring: ldap: urls: ldap://localhost:389 base: dc=example,dc=com user-search-base: ou=people user-search-filter: (uid={0}) group-search-base: ou=groups group-search-filter: (uniqueMember={0}) group-role-attribute: cn manager-dn: cn=admin,dc=example,dc=com manager-password: admin ``` 这里配置了 LDAP 服务器的 URL、基础 DN、用户搜索基础 DN、用户搜索过滤器、组搜索基础 DN、组搜索过滤器、组角色属性、管理员 DN 和管理员密码。 3. 创建 UserDetailsServiceImpl 类 创建一个实现 UserDetailsService 接口的类,用于从 LDAP 中加载用户详细信息。 ```java @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private LdapTemplate ldapTemplate; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List<UserDetails> users = ldapTemplate.search( LdapQueryBuilder.query().where("uid").is(username), (AttributesMapper<UserDetails>) attrs -> { String password = (String) attrs.get("userPassword").get(); List<GrantedAuthority> authorities = new ArrayList<>(); attrs.getAll().stream() .filter(attr -> attr.getID().startsWith("memberOf")) .flatMap(attr -> LdapUtils.convertValueToList(attr).stream()) .forEach(groupDn -> { String groupName = LdapUtils.getRdnValue(new LdapName(groupDn)).toString(); authorities.add(new SimpleGrantedAuthority("ROLE_" + groupName)); }); return new User(username, password, authorities); }); if (users.isEmpty()) { throw new UsernameNotFoundException("User not found"); } return users.get(0); } } ``` 这里使用 LdapTemplate 搜索 LDAP 目录,将查询结果转换为 UserDetails 对象,并返回该对象。 4. 创建 WebSecurityConfig 类 创建一个继承 WebSecurityConfigurerAdapter 的类,用于配置 Spring Security。 ```java @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessURL("/") .permitAll() .and() .logout() .logoutUrl("/logout") .permitAll(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } } ``` 这里配置了 HTTP 访问策略和登录、注销页面的 URL,并将 UserDetailsService 注册到 AuthenticationManagerBuilder 中。 5. 创建登录和注销页面 创建一个登录页面和一个注销页面,用于在浏览器中进行身份验证和注销。例如: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <h1>Login</h1> <form action="/login" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="username"><br><br> <label for="password">Password:</label> <input type="password" id="password" name="password"><br><br> <input type="submit" value="Login"> </form> </body> </html> ``` ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Logout</title> </head> <body> <h1>Logout</h1> <form action="/logout" method="post"> <input type="submit" value="Logout"> </form> </body> </html> ``` 6. 运行应用程序 现在可以启动应用程序并在浏览器中访问它。输入正确的用户名和密码,应该可以成功登录登录后,可以访问受保护的资源。注销后,应该不能访问受保护的资源。 以上是一个基本的 LDAP 认证示例,可以根据实际需求进行修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值