【发布时间】:2018-01-10 22:33:33
【问题描述】:
我们在选择理想的方法来处理分布式应用程序中的身份验证和权限时遇到了问题:
后端由 JAVA EE (Spring Boot) / Tomcat Server 开发
前端由 Angular 4 / NodeJS Server 开发
我们使用 Spring Security Framework,到目前为止,我们已经使用了 3 个可用选项(HTTP Basic、JWT 和 Oauth2),同时尝试调整 Jhipster 项目生成的代码,但不幸的是,其中一个仍然停留在信息的恢复中想要进行身份验证的用户。
换句话说,用户输入他的登录名和密码,服务器通过 URL (http://localhost:8080/api/authenticate) 接收此信息
通过在 java 和客户端的调试,我看到服务器已经恢复(用户名/密码),并以以下形式向浏览器发送令牌(对于 Oauth2 的情况):
{"access_token":"1b0ac85d-b4ed-463f-af3a-acbce7d28353","token_type":"bearer","refresh_token":"ed2f9041-7726-4939-93ba-4816503e3859","expires_in":1799, "范围":"读写"}
但在那之后我必须检索该用户的信息以将它们存储在 LocalStorage (Angular 4) 中并重用它们,这就是为什么在调用上面引用的第一个 Web 服务之后,我调用第二个 Web 服务: (http://localhost:8080/auth/account)
此时,检索数据库信息的链接函数发送一个用户名 / pwd = null 的查询,这给了我们一个 500 消息
在java应用程序中:
/**
* GET /authenticate : check if the user is authenticated, and return its
* login.
*
* @param request
* the HTTP request
* @return the login if the user is authenticated
*/
@RequestMapping(value = ApiConstants.API_ANONYME + "/authenticate", method = RequestMethod.GET)
public String isAuthenticated(HttpServletRequest request) {
log.debug("REST request to check if the current user is authenticated");
String remoteUser = request.getRemoteUser();
return remoteUser;
}
/**
* GET /account : get the current user.
*
* @return the ResponseEntity with status 200 (OK) and the current user in
* body, or status 500 (Internal Server Error) if the user couldn't
* be returned
*/
// @GetMapping("/account")
@RequestMapping(value = ApiConstants.API_AUTH + "/account", method = RequestMethod.GET)
public ResponseEntity<Utilisateur> getAccount() {
Utilisateur userWithAuthorities = userService.getUserWithAuthorities();
return Optional.ofNullable(userWithAuthorities)
.map(user -> new ResponseEntity<>(new Utilisateur(), HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
在 Angular 4 应用程序中:
export class ConnexionBodyComponent implements OnInit {
// model = new Login("", "");
authenticationError: boolean;
password: string;
rememberMe: boolean;
username: string;
credentials: any;
constructor(private loginService: LoginService, private router: Router,
private eventManager: EventManager, private stateStorageService:
StateStorageService) {
}
ngOnInit() {
}
login() {
this.loginService.login({
username: this.username,
password: this.password,
rememberMe: this.rememberMe
}).then(() => {
this.authenticationError = false;
/*if (this.router.url === '/register' ||
(/activate/.test(this.router.url)) ||
this.router.url === '/finishReset' || this.router.url ===
'/requestReset') {
this.router.navigate(['']);
}*/
console.log(this.authenticationError);
this.eventManager.broadcast({
name: 'authenticationSuccess',
content: 'Sending Authentication Success'
});
// // previousState was set in the authExpiredInterceptor before being
redirected to login modal.
// // since login is succesful, go to stored previousState and clear
previousState
const redirect = this.stateStorageService.getUrl();
if (redirect) {
this.router.navigate([redirect]);
}
}).catch(() => {
this.authenticationError = true;
});
}
}
帐户服务:
@Injectable()
export class AccountService {
constructor(private http: Http) { }
get(): Observable<any> {
return
this.http.get(LocalhostSettings.API_ENDPOINT+'/auth/account').map((res:
Response) => res.json());
}
save(account: any): Observable<Response> {
return
this.http.post(LocalhostSettings.API_ENDPOINT+'/auth/account',
account);
}
}
auth.service.ts:
@Injectable()
export class AuthService {
constructor(
private principal: Principal,
private stateStorageService: StateStorageService,
private router: Router
) {}
authorize(force) {
const authReturn = this.principal.identity(force).then(authThen.bind(this));
return authReturn;
function authThen() {
const isAuthenticated = this.principal.isAuthenticated();
const toStateInfo = this.stateStorageService.getDestinationState().destination;
// an authenticated user can't access to login and register pages
if (isAuthenticated && (toStateInfo.name === 'register')) {
this.router.navigate(['']);
return false;
}
// recover and clear previousState after external login redirect (e.g. oauth2)
const fromStateInfo = this.stateStorageService.getDestinationState().from;
const previousState = this.stateStorageService.getPreviousState();
if (isAuthenticated && !fromStateInfo.name && previousState) {
this.stateStorageService.resetPreviousState();
this.router.navigate([previousState.name], { queryParams: previousState.params });
return false;
}
if (toStateInfo.data.authorities && toStateInfo.data.authorities.length > 0) {
return this.principal.hasAnyAuthority(toStateInfo.data.authorities).then((hasAnyAuthority) => {
if (!hasAnyAuthority) {
if (isAuthenticated) {
// user is signed in but not authorized for desired state
this.router.navigate(['accessdenied']);
} else {
// user is not authenticated. Show the state they wanted before you
// send them to the login service, so you can return them when you're done
const toStateParamsInfo = this.stateStorageService.getDestinationState().params;
this.stateStorageService.storePreviousState(toStateInfo.name, toStateParamsInfo);
// now, send them to the signin state so they can log in
this.router.navigate(['accessdenied']).then(() => {
console.log('accessdenied');
});
}
}
return hasAnyAuthority;
});
}
return true;
}
}
}
auth-oauth2.service.ts :
@Injectable()
export class AuthServerProvider {
constructor(
private http: Http,
private base64: Base64,
private $localStorage: LocalStorageService
) {}
getToken() {
return this.$localStorage.retrieve('authenticationToken');
}
login(credentials): Observable<any> {
const data = 'username=' + encodeURIComponent(credentials.username) + '&password=' +
encodeURIComponent(credentials.password) + '&grant_type=password&scope=read%20write&' +
'client_secret=my-secret-token-to-change-in-production&client_id=directinfoapp';
const headers = new Headers ({
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'Authorization': 'Basic ' + this.base64.encode('directinfoapp' + ':' + 'my-secret-token-to-change-in-production')
});
/*
{"access_token":"ff102054-2072-4c29-91a8-c8f43246a3b7","token_type":"bearer","refresh_token":"94b68064-98a6-49e5-9dbc-0cac34e434f3","expires_in":1799,"scope":"read write"}
*/
console.log("headers : " + headers);
return this.http.post(LocalhostSettings.API_ENDPOINT+'/oauth/token', data, {
headers
}).map(authSuccess.bind(this));
function authSuccess(resp) {
const response = resp.json();
console.log("authSuccess " + resp.json())
const expiredAt = new Date();
expiredAt.setSeconds(expiredAt.getSeconds() + response.expires_in);
response.expires_at = expiredAt.getTime();
this.$localStorage.store('authenticationToken', response);
return response;
}
}
logout(): Observable<any> {
return new Observable(observer => {
this.http.post('api/logout', {});
this.$localStorage.clear('authenticationToken');
observer.complete();
});
}
}
login.service.ts
@Injectable()
export class LoginService {
constructor(
private principal: Principal,
private authServerProvider: AuthServerProvider
) {}
login(credentials, callback?) {
const cb = callback || function() {};
return new Promise((resolve, reject) => {
this.authServerProvider.login(credentials).subscribe((data) => {
this.principal.identity(true).then((account) => {
// After the login the language will be changed to
// the language selected by the user during his registration
if (account !== null) {
account.langKey;
}
resolve(data);
});
return cb();
}, (err) => {
this.logout();
reject(err);
return cb(err);
});
});
}
logout() {
this.authServerProvider.logout().subscribe();
this.principal.authenticate(null);
}
}
主要服务:
@Injectable()
export class Principal {
private userIdentity: any;
private authenticated = false;
private authenticationState = new Subject<any>();
constructor(
private account: AccountService
) {}
authenticate(identity) {
this.userIdentity = identity;
this.authenticated = identity !== null;
this.authenticationState.next(this.userIdentity);
}
hasAnyAuthority(authorities: string[]): Promise<boolean> {
if (!this.authenticated || !this.userIdentity || !this.userIdentity.authorities) {
return Promise.resolve(false);
}
for (let i = 0; i < authorities.length; i++) {
if (this.userIdentity.authorities.indexOf(authorities[i]) !== -1) {
return Promise.resolve(true);
}
}
return Promise.resolve(false);
}
hasAuthority(authority: string): Promise<boolean> {
if (!this.authenticated) {
return Promise.resolve(false);
}
return this.identity().then((id) => {
return Promise.resolve(id.authorities && id.authorities.indexOf(authority) !== -1);
}, () => {
return Promise.resolve(false);
});
}
identity(force?: boolean): Promise<any> {
if (force === true) {
this.userIdentity = undefined;
}
// check and see if we have retrieved the userIdentity data from the server.
// if we have, reuse it by immediately resolving
if (this.userIdentity) {
return Promise.resolve(this.userIdentity);
}
// retrieve the userIdentity data from the server, update the identity object, and then resolve.
return this.account.get().toPromise().then((account) => {
if (account) {
this.userIdentity = account;
this.authenticated = true;
} else {
this.userIdentity = null;
this.authenticated = false;
}
this.authenticationState.next(this.userIdentity);
return this.userIdentity;
}).catch((err) => {
this.userIdentity = null;
this.authenticated = false;
this.authenticationState.next(this.userIdentity);
return null;
});
}
isAuthenticated(): boolean {
return this.authenticated;
}
isIdentityResolved(): boolean {
return this.userIdentity !== undefined;
}
getAuthenticationState(): Observable<any> {
return this.authenticationState.asObservable();
}
getImageUrl(): String {
return this.isIdentityResolved() ? this.userIdentity.imageUrl : null;
}
}
有什么想法可以完成这项工作吗?
【问题讨论】:
-
恐怕你的问题太长而且不太清楚。也许您可以编辑它并添加您想要实现的高级概述(谁需要 OAuth2 令牌,您使用什么 OAuth2 流程)以及您尝试过的内容。为了描述身份验证过程,您可以使用一个步骤列表,其中每个步骤都说明由谁执行了哪些操作以及结果是什么。
-
我的目标是管理 Angular 4 应用程序和 Spring Boot 应用程序之间的安全部分,但我不知道执行此操作的理想选择(Ouath 2、HTTP Basic 或 JWT),如果你有教程或链接我可以在哪里找到更多详细信息我会很感激你
标签: angular spring-boot spring-security oauth-2.0 jhipster