이번에는 Vue+Jwt+SpringBoot+Ldap을 사용하여 로그인 인증을 완료하는 방법과 Vue+Jwt+SpringBoot+Ldap을 사용하여 로그인 인증을 완료할 때 주의사항이 무엇인지 보여드리겠습니다. 살펴보자.
기존의 기존 로그인 인증 방식은 기본적으로 서버 측에서 로그인 페이지를 제공했는데, 페이지의 양식에 사용자 이름과 비밀번호를 입력한 후 서버에서 이 정보를 DB 또는 Ldap의 사용자 정보와 비교했습니다. , 이 사용자 정보는 세션에 기록됩니다.
여기서 나는 첫 번째 큰 구덩이에 뛰어들었습니다. 기존 방식에서는 프런트엔드와 백엔드가 분리되지 않고 백엔드가 페이지 렌더링을 담당하지만, 프런트엔드와 백엔드가 분리되면 백엔드는 노출된 RestApi를 통한 데이터 제공만 담당하게 되며, 페이지의 렌더링 및 라우팅은 프런트 엔드에서 완료됩니다. REST는 상태 비저장이므로 서버에 기록된 세션이 없습니다.
이전에는 SpringSecurity+Cas+Ldap을 사용하여 SSO를 했는데, Vue를 프런트엔드로 사용한 후에는 이전 방법으로 SSO를 할 생각을 할 수 없었습니다(빠지는 데 시간이 오래 걸렸습니다). 이 구덩이). 나중에 위에서 언급한 세션 문제를 드디어 알아냈습니다(그렇다고 생각했는데 틀렸을 수도 있습니다. CAS에도 RestApi가 있는데 공식 홈페이지에 따라 구성이 안되서 포기했습니다).
첫 번째 질문은 SSO 문제를 해결하는 방법입니다. JWT에 대해 이야기해 보겠습니다. JWT는 스펙이며, 다양한 언어로 구현되어 있습니다. 공식 홈페이지에서 확인하실 수 있습니다. 내 피상적인 이해는 인증 서비스(직접 작성, Db, Ldap 등)가 있다는 것입니다. 이 인증 서비스는 사용자가 제출한 정보를 기반으로 인증 성공 여부를 판단합니다. 성공하면 일부 사용자 정보(사용자)를 쿼리합니다. 이름, 역할 등), JWT는 이 정보를 토큰으로 암호화하여 클라이언트 브라우저에 반환합니다. 브라우저는 나중에 리소스에 액세스할 때마다 이 정보를 헤더에 전달합니다. 서버는 요청을 받은 후 이를 암호화 시와 동일한 키로 복호화하는데 성공하면 사용자가 인증된 것으로 간주되며(물론 암호화 중에 만료 시간을 추가할 수 있음) 완료되었습니다. 해독된 역할 정보를 사용하면 이 사용자에게 일부 서비스를 수행할 수 있는 권한이 있는지 확인할 수 있습니다. 이렇게 하고 나면 SPA 애플리케이션에서 SSO에 SpringSecurity와 Cas가 아무런 역할도 하지 않는 것 같은 느낌이 듭니다. (물론 틀렸을 수도 있습니다)
첫 번째 문제는 거의 해결되었으니 두 번째 문제에 대해 이야기해 보겠습니다. . 이전에는 세션이 존재하기 때문에 보호된 리소스에 액세스할 때 서버에 현재 사용자의 세션이 없으면 강제로 로그인 페이지로 이동해야 했습니다. 전면과 후면이 분리된 경우 이 요구 사항을 달성하는 방법. 아이디어는 다음과 같습니다. Vue-Router의 전역 라우팅 후크를 사용하여 페이지에 액세스할 때 localStorage에 JWT 암호화 토큰이 있는지 먼저 확인하고 토큰이 만료되지 않은 경우 요청된 페이지로 이동합니다. 그렇지 않은 경우 존재하거나 만료된 경우 재인증을 위해 로그인 페이지로 이동합니다.
아이디어는 충분하고 코드로 넘어가겠습니다
1 먼저 LDAP가 필요합니다. 저는 AD를 사용합니다. 여기서는 minibox.com이라는 도메인을 만들고 2개의 하위 OU가 있는 Employees OU를 추가하고 하위 OU에 2명의 사용자를 만들었습니다.
그룹스에서 새 그룹을 만들고 이전에 만든 사용자를 그룹에 추가하여 사용자가 역할을 갖도록 합니다. build build build build bule reee
2.2 application<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>minibox</groupId> <artifactId>an</artifactId> <version>0.0.1-SNAPSHOT</version> <!-- Inherit defaults from Spring Boot --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <!-- Add typical dependencies for a web application --> <dependencies> <!-- MVC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring boot test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- spring-boot-starter-hateoas --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency> <!-- 热启动 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> <!-- Spring Ldap --> <dependency> <groupId>org.springframework.ldap</groupId> <artifactId>spring-ldap-core</artifactId> <version>2.3.1.RELEASE</version> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency> </dependencies> <!-- Package as an executable jar --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- Hot swapping --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> <version>1.2.0.RELEASE</version> </dependency> </dependencies> </plugin> </plugins> </build> </project>
4.1 Vue-cli를 사용하여 프로젝트를 빌드하고, 이해가 되지 않으면 vue-router 및 vue-resource를 사용하여 검색할 수 있습니다.
4.2 main.js
#Logging_config logging.level.root=INFO logging.level.org.springframework.web=WARN logging.file=minibox.log #server_config #使用了SSL,并且在ldap配置中使用了ldaps,这里同时也需要把AD的证书导入到server.keystore中。具体的可以查看java的keytool工具 server.port=8443 server.ssl.key-store=classpath:server.keystore server.ssl.key-store-password=minibox server.ssl.key-password=minibox #jwt #jwt加解密时使用的key jwt.key=minibox #ldap_config #ldap配置信息,注意这里的userDn一定要写这种形式。referral设置为follow,说不清用途,似乎只有连接AD时才需要配置 ldap.url=ldaps://192.168.227.128:636 ldap.base=ou=Employees,dc=minibox,dc=com ldap.userDn=cn=Administrator,cn=Users,dc=minibox,dc=com ldap.userPwd=qqq111!!!! ldap.referral=follow ldap.domainName=@minibox.com
package an.auth; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.ldap.NamingException; import org.springframework.ldap.core.AttributesMapper; import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.support.LdapUtils; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import static org.springframework.ldap.query.LdapQueryBuilder.query; import java.util.Date; import java.util.HashMap; import java.util.Map; import an.entity.Employee; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @RestController @RequestMapping("/auth") public class JwtAuth { //jwt加密密匙 @Value("${jwt.key}") private String jwtKey; //域名后缀 @Value("${ldap.domainName}") private String ldapDomainName; //ldap模板 @Autowired private LdapTemplate ldapTemplate; /** * 将域用户属性通过EmployeeAttributesMapper填充到Employee类中,返回一个填充信息的Employee实例 */ private class EmployeeAttributesMapper implements AttributesMapper<Employee> { public Employee mapFromAttributes(Attributes attrs) throws NamingException, javax.naming.NamingException { Employee employee = new Employee(); employee.setName((String) attrs.get("sAMAccountName").get()); employee.setDisplayName((String) attrs.get("displayName").get()); employee.setRole((String) attrs.get("memberOf").toString()); return employee; } } /** * @param username 用户提交的名称 * @param password 用户提交的密码 * @return 成功返回加密后的token信息,失败返回错误HTTP状态码 */ @CrossOrigin//因为需要跨域访问,所以要加这个注解 @RequestMapping(method = RequestMethod.POST) public ResponseEntity<String> authByAd( @RequestParam(value = "username") String username, @RequestParam(value = "password") String password) { //这里注意用户名加域名后缀 userDn格式:anwx@minibox.com String userDn = username + ldapDomainName; //token过期时间 4小时 Date tokenExpired = new Date(new Date().getTime() + 60*60*4*1000); DirContext ctx = null; try { //使用用户名、密码验证域用户 ctx = ldapTemplate.getContextSource().getContext(userDn, password); //如果验证成功根据sAMAccountName属性查询用户名和用户所属的组 Employee employee = ldapTemplate .search(query().where("objectclass").is("person").and("sAMAccountName").is(username), new EmployeeAttributesMapper()) .get(0); //使用Jwt加密用户名和用户所属组信息 String compactJws = Jwts.builder() .setSubject(employee.getName()) .setAudience(employee.getRole()) .setExpiration(tokenExpired) .signWith(SignatureAlgorithm.HS512, jwtKey).compact(); //登录成功,返回客户端token信息。这里只加密了用户名和用户角色,而displayName和tokenExpired没有加密 Map<String, Object> userInfo = new HashMap<String, Object>(); userInfo.put("token", compactJws); userInfo.put("displayName", employee.getDisplayName()); userInfo.put("tokenExpired", tokenExpired.getTime()); return new ResponseEntity<String>(JSON.toJSONString(userInfo , SerializerFeature.DisableCircularReferenceDetect) , HttpStatus.OK); } catch (Exception e) { //登录失败,返回失败HTTP状态码 return new ResponseEntity<String>(HttpStatus.UNAUTHORIZED); } finally { //关闭ldap连接 LdapUtils.closeContext(ctx); } } }
5 효과
5.1 http://localhost:8000 접속 시 로그인 페이지로 이동됩니다
5.2 로그인 정보를 입력하고 토큰을 획득하면 다음 페이지로 이동합니다.이 기사의 사례를 읽으신 후 방법을 마스터하셨다고 믿습니다. 더 흥미로운 내용을 보려면 PHP 중국어 웹사이트의 다른 관련 기사를 주목하세요!
추천 도서:
Vue를 사용하여 AdminLTE 템플릿을 통합하는 방법
위 내용은 Vue+Jwt+SpringBoot+Ldap을 사용하여 로그인 인증을 완료하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!