java - Spring Security + Angular 7 | LDAP 身份验证
问题描述
我有一个大问题。我现在在这里坐了几天,想着如何让我的登录应用程序正常工作。我希望它将用户名和密码发送到检查它的spring boot。Spring boot 中的基本内容已经在工作(loginForm)。我在使用 Spring Boot 编程方面非常陌生,因为我之前做过 NodeJS。目标是在后端通过 LDAP 验证用户,但使用 Spring Security 而不是纯Java(虽然很容易)。整个事情当然是HTTP 请求(端点)
这是我到目前为止所拥有的:
// WebSecurityConfiguration
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/test").permitAll()
.usernameParameter("_username_")
.passwordParameter("_password_")
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org")
.and()
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
}
@Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource(Arrays.asList("ldap://localhost:8389/"), "dc=springframework,cd=org");
}
我使用 Gradle 是因为我们在公司默认使用它,所以这里是build.gradle:
// build.gradle
plugins {
id 'org.springframework.boot' version '2.1.2.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'net.company'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-ldap'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-web-services'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.boot:spring-boot-starter-security'
runtimeOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'mysql:mysql-connector-java'
compileOnly 'org.projectlombok:lombok'
compile("org.springframework.ldap:spring-ldap-core")
compile("org.springframework.security:spring-security-ldap")
compile("org.springframework:spring-tx")
compile("com.unboundid:unboundid-ldapsdk")
compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.4'
compile group: 'org.glassfish.jaxb', name: 'jaxb-core', version: '2.3.0.1'
compile group: 'javax.xml', name: 'jaxb-impl', version: '2.1'
compile group: 'javax.activation', name: 'activation', version: '1.1.1'
testCompile("org.springframework.boot:spring-boot-starter-test")
testCompile("org.springframework.security:spring-security-test")
}
这就是我现在的app.component.ts。我已经导入了 HttpClientModule 并将其添加到导入中:
// app.component.ts
import {Component} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Memory';
status: string;
greeting = {};
private serverUrl = 'http://localhost:8082/';
private stompClient;
constructor(private http: HttpClient) {
this.initializeWebSocketConnection();
http.get(this.serverUrl + 'login').subscribe(data => data);
}
initializeWebSocketConnection() {
let ws = new SockJS(this.serverUrl);
this.stompClient = Stomp.over(ws);
let that = this;
this.stompClient.connect({}, function(frame) {
that.stompClient.subscribe('/chat', (message) => {
console.log(message);
});
});
}
sendMessage(msg) {
this.stompClient.send('/app/send/message', {}, msg);
}
ngOnInit() {
this.setStatus('login');
setTimeout(() => {
this.sendMessage('test =>');
}, 5000);
}
setStatus(status): void {
this.status = status;
}
}
// login.component.html
<div id="container"
fxLayout="column"
fxLayoutAlign="start center">
<div class="spacer" fxFlex="10%"></div>
<div id="login" fxFlex="25%"
fxLayout="column"
fxLayoutAlign="start center">
<h1 id="loginTitle" fxFlex="35%">LOGIN</h1>
<label for="user"></label><input id="user" placeholder="username" fxFlex="17.5%" onkeydown="if (event.keyCode == 13) document.getElementById('loginButton').click()"/>
<div class="spacer" fxFlex="10%"></div>
<label for="password"></label><input type="password" id="password" placeholder="password" fxFlex="17.5%" onkeydown="if (event.keyCode == 13) document.getElementById('loginButton').click()"/>
<div class="spacer" fxFlex="10%"></div>
<img id="loginButton" src="../../assets/login/img/right-arrow.png" fxFlex="10%" alt="">
</div>
<div id="spacer" fxFlex="74%"></div>
<div id="attention" fxFlex="1%">Icons made by <a href="https://www.flaticon.com/authors/lyolya" title="Lyolya">Lyolya</a></div>
</div>
这里是那些需要有关LDAP信息的人的application.properties(application.yml目前为空):
// application.properties
spring.ldap.embedded.ldif=classpath:test-server.ldif
spring.ldap.embedded.base-dn=dc=springframework,dc=org
spring.ldap.embedded.port=8389
server.servlet.context-path=/
server.port=8082
如果您需要更多信息或文件,请 PM 我。 如果我们得到一个解决方案,我将重构这篇文章,以便所有初学者都容易理解。
解决方案
我认为您需要添加自定义过滤器实现。我建议阅读Spring Security Documentation,查看第 9 点。
因此,理想情况下,由于您通过 HTTP&REST 执行此操作,因此您需要任何 JWT 库将令牌传输到 Angular 并在 Spring 后端验证它们。
您应该覆盖UsernamePasswordAuthenticationFilter
并添加您的attemptAuthentication
和successfulAuthentication
方法的实现。
首先,您需要检查发布的用户名和密码是否正确,例如:
try {
LoginDto loginDto = new ObjectMapper().readValue(request.getInputStream(), LoginDto.class);
if (getAuthenticationManager() == null) logger.warn("Authentication manager is null!");
return getAuthenticationManager()
.authenticate(
new UsernamePasswordAuthenticationToken(
loginDto.getEmail(), loginDto.getPassword(), new ArrayList<>()));
} catch (IOException e) {
logger.warn(e.getMessage());
}
throw new AuthenticationServiceException("Credentials are invalid");
在第二个中,您将令牌颁发给您的 Angular 客户端,以便用户稍后可以将其提供给您的后端,例如:
String userName = authResult.getName();
try {
User user = userService.findUserByUserName(userName);
if (user.getAccountLocked()
.getLockDate()
.before(java.sql.Date.valueOf(LocalDate.now().minus(15, ChronoUnit.MINUTES)))) {
throw new UserLockedException();
}
List<String> authorities = new ArrayList<>();
user.getAuthorities().forEach(e -> authorities.add(e.getAuthority()));
Map<String, Object> claims = new LinkedHashMap<>();
claims.put("email", user.getEmail());
claims.put("firstName", user.getFirstName());
claims.put("lastName", user.getLastName());
claims.put("authorities", authorities);
String token =
Jwts.builder()
.setSubject(user.getId())
.addClaims(claims)
.setExpiration(
new Date(System.currentTimeMillis() + Long.parseLong(jwtConfig.getExpiration())))
.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
.compact();
response.setHeader("Access-Control-Expose-Headers", "Authorization");
response.setHeader("Authorization", "Bearer " + token);
userService.successfulLogin(user);
userService.resetLoginAttempts(user);
} catch (NotFoundException e) {
logger.warn(e.getMessage());
}
}
将此过滤器添加到您的 http 配置中:
Collections.singletonList(daoAuthenticationProvider()));
AuthenticationFilter filter = new AuthenticationFilter(userService,manager, mapper, jwtConfig);
filter.setFilterProcessesUrl("/login");
http.authorizeRequests().antMatchers("/**").permitAll()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilter(filter);
推荐阅读
- asp.net-core - 如何在实体框架核心中为关系表访问应用分页
- python - 使用 pandas 写入现有的 XLS 文件
- angular - How to return autocomplete values only in a specific radius in Google Autocomplete (ngx-autocom-place)?
- powershell - 重命名文件夹,并与现有文件夹合并(如果它存在于 Powershell 中)
- node.js - Node js GraphQL Root Mutation 传递一个数组
- reactjs - Next.js 链接在 /api 路由上获得 404 状态码
- java - 为什么不能两次调用 scanEverything 方法?它只打印出arrayList一次,第二个println显示一个空列表
- sql - 添加 IF ELSE 语句
- c++ - 我们可以定义两个同名但参数不同的函数吗?
- html - Vue.js - 根据选择更改数学运算符?