提交 0f912df2 编写于 作者: M MaxKey

CONGRESS

上级 eb748ac8
......@@ -25,14 +25,30 @@ import org.maxkey.authn.SignPrincipal;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import com.fasterxml.jackson.annotation.JsonProperty;
public class AuthJwt implements Serializable {
private static final long serialVersionUID = -914373258878811144L;
public static final String ACCESS_TOKEN = "access_token";
public static final String REFRESH_TOKEN = "refresh_token";
public static final String EXPIRES_IN = "expired";
private String ticket;
private String type = "Bearer";
private String token;
@JsonProperty(REFRESH_TOKEN)
private String refreshToken;
private String type = "Bearer";
@JsonProperty(EXPIRES_IN)
private int expiresIn;
private String remeberMe;
private String id;
private String name;
......@@ -44,27 +60,36 @@ public class AuthJwt implements Serializable {
private int passwordSetType;
private List<String> authorities;
public AuthJwt(String token, String id, String username, String displayName, String email, String instId,
String instName, List<String> authorities) {
public AuthJwt(String ticket, String type, String token, String refreshToken, int expiresIn, String remeberMe,
String id, String name, String username, String displayName, String email, String instId, String instName,
int passwordSetType, List<String> authorities) {
super();
this.ticket = ticket;
this.type = type;
this.token = token;
this.refreshToken = refreshToken;
this.expiresIn = expiresIn;
this.remeberMe = remeberMe;
this.id = id;
this.name = username;
this.name = name;
this.username = username;
this.displayName = displayName;
this.email = email;
this.instId = instId;
this.instName = instName;
this.passwordSetType = passwordSetType;
this.authorities = authorities;
}
public AuthJwt(String token,String refreshToken, Authentication authentication) {
public AuthJwt(String token, Authentication authentication,int expiresIn,String refreshToken) {
SignPrincipal principal = ((SignPrincipal)authentication.getPrincipal());
this.token = token;
this.expiresIn = expiresIn;
this.refreshToken = refreshToken;
this.ticket = principal.getSession().getId();
this.ticket = principal.getSession().getId();
this.id = principal.getUserInfo().getId();
this.username = principal.getUserInfo().getUsername();
this.name = this.username;
......@@ -175,6 +200,16 @@ public class AuthJwt implements Serializable {
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public int getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}
@Override
public String toString() {
......
......@@ -7,6 +7,7 @@ import org.joda.time.DateTime;
import org.maxkey.authn.SignPrincipal;
import org.maxkey.crypto.jwt.HMAC512Service;
import org.maxkey.entity.UserInfo;
import org.maxkey.util.StringUtils;
import org.maxkey.web.WebContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -33,7 +34,7 @@ public class AuthJwtService {
DateTime currentDateTime = DateTime.now();
String subject = principal.getUsername();
Date expirationTime = currentDateTime.plusSeconds(expires).toDate();
_logger.debug("jwt subject : {} , expiration Time : {}" , subject,expirationTime);
_logger.trace("jwt subject : {} , expiration Time : {}" , subject,expirationTime);
JWTClaimsSet jwtClaims =new JWTClaimsSet.Builder()
.issuer(issuer)
......@@ -102,12 +103,14 @@ public class AuthJwtService {
*/
public boolean validateJwtToken(String authToken) {
try {
JWTClaimsSet claims = resolve(authToken);
boolean isExpiration = claims.getExpirationTime().after(DateTime.now().toDate());
boolean isVerify = hmac512Service.verify(authToken);
_logger.debug("JWT Verify {} , now {} , ExpirationTime {} , isExpiration : {}" ,
isVerify,DateTime.now().toDate(),claims.getExpirationTime(),isExpiration);
return isVerify && isExpiration;
if(StringUtils.isNotBlank(authToken)) {
JWTClaimsSet claims = resolve(authToken);
boolean isExpiration = claims.getExpirationTime().after(DateTime.now().toDate());
boolean isVerify = hmac512Service.verify(authToken);
_logger.trace("JWT Verify {} , now {} , ExpirationTime {} , isExpiration : {}" ,
isVerify,DateTime.now().toDate(),claims.getExpirationTime(),isExpiration);
return isVerify && isExpiration;
}
} catch (ParseException e) {
_logger.error("authToken {}",authToken);
_logger.error("ParseException ",e);
......
......@@ -66,13 +66,22 @@ public class AuthTokenService extends AuthJwtService{
public AuthJwt genAuthJwt(Authentication authentication) {
if(authentication != null) {
String refreshToken = refreshTokenService.genRefreshToken(authentication);
return new AuthJwt(genJwt(authentication),refreshToken, authentication);
String accessToken = genJwt(authentication);
AuthJwt authJwt = new AuthJwt(
accessToken,
authentication,
authJwkConfig.getExpires(),
refreshToken);
return authJwt;
}
return null;
}
public String genJwt(Authentication authentication) {
return genJwt( authentication,authJwkConfig.getIssuer(),authJwkConfig.getExpires());
return genJwt(
authentication,
authJwkConfig.getIssuer(),
authJwkConfig.getExpires());
}
......@@ -100,8 +109,9 @@ public class AuthTokenService extends AuthJwtService{
congress,
new AuthJwt(
genJwt(authentication),
refreshToken,
authentication)
authentication,
authJwkConfig.getExpires(),
refreshToken)
);
return congress;
}
......
......@@ -37,18 +37,23 @@ import org.springframework.security.core.Authentication;
public class AuthorizationUtils {
private static final Logger _logger = LoggerFactory.getLogger(AuthorizationUtils.class);
public static final String Authorization_Cookie = "congress";
public static final class BEARERTYPE{
public static final String CONGRESS = "congress";
public static final String AUTHORIZATION = "Authorization";
}
public static void authenticateWithCookie(
HttpServletRequest request,
AuthTokenService authTokenService,
SessionManager sessionManager
) throws ParseException{
Cookie authCookie = WebContext.getCookie(request, Authorization_Cookie);
Cookie authCookie = WebContext.getCookie(request, BEARERTYPE.CONGRESS);
if(authCookie != null ) {
String authorization = authCookie.getValue();
doJwtAuthenticate(authorization,authTokenService,sessionManager);
_logger.debug("congress automatic authenticated .");
_logger.trace("Try congress authenticate .");
doJwtAuthenticate(BEARERTYPE.CONGRESS,authorization,authTokenService,sessionManager);
}
}
......@@ -59,13 +64,14 @@ public class AuthorizationUtils {
) throws ParseException{
String authorization = AuthorizationHeaderUtils.resolveBearer(request);
if(authorization != null ) {
doJwtAuthenticate(authorization,authTokenService,sessionManager);
_logger.debug("Authorization automatic authenticated .");
_logger.trace("Try Authorization authenticate .");
doJwtAuthenticate(BEARERTYPE.AUTHORIZATION,authorization,authTokenService,sessionManager);
}
}
public static void doJwtAuthenticate(
String bearerType,
String authorization,
AuthTokenService authTokenService,
SessionManager sessionManager) throws ParseException {
......@@ -75,12 +81,17 @@ public class AuthorizationUtils {
Session session = sessionManager.get(sessionId);
if(session != null) {
setAuthentication(session.getAuthentication());
_logger.debug("{} Automatic authenticated .",bearerType);
}else {
setAuthentication(null);
//time out
_logger.debug("Session timeout .");
clearAuthentication();
}
}
}else {
setAuthentication(null);
//token invalidate
_logger.debug("Token invalidate .");
clearAuthentication();
}
}
......@@ -100,6 +111,10 @@ public class AuthorizationUtils {
WebContext.setAttribute(WebConstants.AUTHENTICATION, authentication);
}
public static void clearAuthentication() {
WebContext.removeAttribute(WebConstants.AUTHENTICATION);
}
public static boolean isAuthenticated() {
return getAuthentication() != null;
}
......
......@@ -33,6 +33,7 @@ public class LoginRefreshPoint {
@RequestMapping(value={"/token/refresh"}, produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<?> refresh(
@RequestHeader(name = "refresh_token", required = true) String refreshToken) {
_logger.debug("try to refresh token " );
_logger.trace("refresh token {} " , refreshToken);
try {
if(refreshTokenService.validateJwtToken(refreshToken)) {
......@@ -47,7 +48,7 @@ public class LoginRefreshPoint {
_logger.debug("Session is timeout , sessionId [{}]" , sessionId);
}
}else {
_logger.trace("refresh token is not validate .");
_logger.debug("refresh token is not validate .");
}
}catch(Exception e) {
_logger.error("Refresh Exception !",e);
......
......@@ -22,6 +22,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import org.maxkey.authn.annotation.CurrentUser;
import org.maxkey.authn.jwt.AuthTokenService;
import org.maxkey.authn.web.AuthorizationUtils;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.provider.AuthorizationRequest;
......@@ -81,6 +82,9 @@ public class OAuth20AccessConfirmationEndpoint {
@Autowired
protected ApplicationConfig applicationConfig;
@Autowired
AuthTokenService authTokenService;
/**
* getAccessConfirmation.
* @param model Map
......@@ -95,7 +99,7 @@ public class OAuth20AccessConfirmationEndpoint {
AuthorizationRequest clientAuth =
(AuthorizationRequest) momentaryService.get(currentUser.getSessionId(), "authorizationRequest");
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId(),true);
model.put("oauth_approval", WebContext.genId());
model.put("oauth_approval", authTokenService.genRandomJwt());
model.put("auth_request", clientAuth);
model.put("client", client);
model.put("oauth_version", "oauth 2.0");
......@@ -136,7 +140,7 @@ public class OAuth20AccessConfirmationEndpoint {
@PathVariable("oauth_approval") String oauth_approval,
@CurrentUser UserInfo currentUser) {
Map<String, Object> model = new HashMap<String, Object>();
if(StringUtils.isNotBlank(oauth_approval)) {
if(authTokenService.validateJwtToken(oauth_approval)) {
try {
AuthorizationRequest clientAuth =
(AuthorizationRequest) momentaryService.get(currentUser.getSessionId(), "authorizationRequest");
......
......@@ -45,6 +45,7 @@ export class DefaultInterceptor implements HttpInterceptor {
private refreshTokenEnabled = environment.api.refreshTokenEnabled;
private refreshTokenType: 're-request' | 'auth-refresh' = environment.api.refreshTokenType;
private refreshToking = false;
private notified = false;
private refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private injector: Injector) {
......@@ -70,7 +71,10 @@ export class DefaultInterceptor implements HttpInterceptor {
}
private goTo(url: string): void {
setTimeout(() => this.injector.get(Router).navigateByUrl(url));
setTimeout(() => {
this.injector.get(Router).navigateByUrl(url);
this.notified = false;
});
}
private checkStatus(ev: HttpResponseBase): void {
......@@ -87,7 +91,7 @@ export class DefaultInterceptor implements HttpInterceptor {
*/
private refreshTokenRequest(): Observable<any> {
const model = this.tokenSrv.get();
return this.http.post(`/auth/token/refresh`, null, null, { headers: { refresh_token: model?.['refreshToken'] || '' } });
return this.http.post(`/auth/token/refresh`, null, null, { headers: { refresh_token: model?.['refresh_token'] || '' } });
}
// #region 刷新Token方式一:使用 401 重新刷新 Token
......@@ -117,7 +121,7 @@ export class DefaultInterceptor implements HttpInterceptor {
console.log(res.data);
// 通知后续请求继续执行
this.refreshToking = false;
this.refreshToken$.next(res.data.refreshToken);
this.refreshToken$.next(res.data.refresh_token);
this.cookieService.set(CONSTS.CONGRESS, res.data.token);
// 重新保存新 token
this.tokenSrv.set(res.data);
......@@ -181,8 +185,11 @@ export class DefaultInterceptor implements HttpInterceptor {
// #endregion
private toLogin(): void {
this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
this.goTo(this.tokenSrv.login_url!);
if (!this.notified) {
this.notified = true;
this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
this.goTo(this.tokenSrv.login_url!);
}
}
private handleData(ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandler): Observable<any> {
......@@ -227,10 +234,7 @@ export class DefaultInterceptor implements HttpInterceptor {
break;
default:
if (ev instanceof HttpErrorResponse) {
console.warn(
'未可知错误,大部分是由于后端不支持跨域CORS或无效配置引起,请参考 https://ng-alain.com/docs/server 解决跨域问题',
ev
);
console.warn('未可知错误,大部分是由于后端不支持跨域CORS或无效配置引起.', ev);
}
break;
}
......
......@@ -78,7 +78,14 @@ import { LayoutDefaultOptions } from '../../theme/layout-default';
<router-outlet></router-outlet>
</ng-template>
</layout-default>
<global-footer style="border-top: 1px solid #e5e5e5; min-height: 120px; text-shadow: 0 1px 0 #fff;margin:0;">
<div style="margin-top: 30px">
MaxKey v3.5.0 GA<br />
Copyright
<i nz-icon nzType="copyright"></i> 2022 <a href="//www.maxkey.top" target="_blank">http://www.maxkey.top</a><br />
Licensed under the Apache License, Version 2.0
</div>
</global-footer>
<setting-drawer *ngIf="showSettingDrawer"></setting-drawer>
<theme-btn></theme-btn>
`
......
......@@ -73,7 +73,7 @@ export class AuthenticationService {
}
this.cookieService.set(CONSTS.CONGRESS, authJwt.token);
this.cookieService.set(CONSTS.CONGRESS, authJwt.ticket, { domain: subHostName });
this.cookieService.set(CONSTS.ONLINE_TICKET, authJwt.ticket, { domain: subHostName });
if (authJwt.remeberMe) {
localStorage.setItem(CONSTS.REMEMBER, authJwt.remeberMe);
}
......
export const CONSTS = {
CONGRESS: 'congress',
ONLINE_TICKET: 'online_ticket',
REDIRECT_URI: 'redirect_uri',
REMEMBER: 'remember_me'
};
......@@ -13,9 +13,12 @@ import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { ALAIN_I18N_TOKEN, _HttpClient } from '@delon/theme';
import { environment } from '@env/environment';
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { CookieService } from 'ngx-cookie-service';
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
import { catchError, filter, mergeMap, switchMap, take } from 'rxjs/operators';
import { CONSTS } from '../../shared/consts';
const CODEMESSAGE: { [key: number]: string } = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
......@@ -42,6 +45,7 @@ export class DefaultInterceptor implements HttpInterceptor {
private refreshTokenEnabled = environment.api.refreshTokenEnabled;
private refreshTokenType: 're-request' | 'auth-refresh' = environment.api.refreshTokenType;
private refreshToking = false;
private notified = false;
private refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private injector: Injector) {
......@@ -54,6 +58,10 @@ export class DefaultInterceptor implements HttpInterceptor {
return this.injector.get(NzNotificationService);
}
private get cookieService(): CookieService {
return this.injector.get(CookieService);
}
private get tokenSrv(): ITokenService {
return this.injector.get(DA_SERVICE_TOKEN);
}
......@@ -63,7 +71,10 @@ export class DefaultInterceptor implements HttpInterceptor {
}
private goTo(url: string): void {
setTimeout(() => this.injector.get(Router).navigateByUrl(url));
setTimeout(() => {
this.injector.get(Router).navigateByUrl(url);
this.notified = false;
});
}
private checkStatus(ev: HttpResponseBase): void {
......@@ -91,6 +102,7 @@ export class DefaultInterceptor implements HttpInterceptor {
this.toLogin();
return throwError(ev);
}
// 2、如果 `refreshToking` 为 `true` 表示已经在请求刷新 Token 中,后续所有请求转入等待状态,直至结果返回后再重新发起请求
if (this.refreshToking) {
return this.refreshToken$.pipe(
......@@ -99,17 +111,20 @@ export class DefaultInterceptor implements HttpInterceptor {
switchMap(() => next.handle(this.reAttachToken(req)))
);
}
// 3、尝试调用刷新 Token
this.refreshToking = true;
this.refreshToken$.next(null);
return this.refreshTokenRequest().pipe(
switchMap(res => {
console.log(res.data);
// 通知后续请求继续执行
this.refreshToking = false;
this.refreshToken$.next(res);
this.refreshToken$.next(res.data.refresh_token);
this.cookieService.set(CONSTS.CONGRESS, res.data.token);
// 重新保存新 token
this.tokenSrv.set(res);
this.tokenSrv.set(res.data);
// 重新发起请求
return next.handle(this.reAttachToken(req));
}),
......@@ -127,11 +142,14 @@ export class DefaultInterceptor implements HttpInterceptor {
* > 由于已经发起的请求,不会再走一遍 `@delon/auth` 因此需要结合业务情况重新附加新的 Token
*/
private reAttachToken(req: HttpRequest<any>): HttpRequest<any> {
//console.log('reAttachToken');
// 以下示例是以 NG-ALAIN 默认使用 `SimpleInterceptor`
const token = this.tokenSrv.get()?.token;
return req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
Authorization: `Bearer ${token}`,
hostname: window.location.hostname,
AuthServer: 'MaxKey'
}
});
}
......@@ -167,8 +185,11 @@ export class DefaultInterceptor implements HttpInterceptor {
// #endregion
private toLogin(): void {
this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
this.goTo(this.tokenSrv.login_url!);
if (!this.notified) {
this.notified = true;
this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
this.goTo(this.tokenSrv.login_url!);
}
}
private handleData(ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandler): Observable<any> {
......@@ -213,10 +234,7 @@ export class DefaultInterceptor implements HttpInterceptor {
break;
default:
if (ev instanceof HttpErrorResponse) {
console.warn(
'未可知错误,大部分是由于后端不支持跨域CORS或无效配置引起,请参考 https://ng-alain.com/docs/server 解决跨域问题',
ev
);
console.warn('未可知错误,大部分是由于后端不支持跨域CORS或无效配置引起.', ev);
}
break;
}
......@@ -237,6 +255,8 @@ export class DefaultInterceptor implements HttpInterceptor {
if (jwtAuthn !== null) {
res['Authorization'] = `Bearer ${jwtAuthn.token}`;
}
res['hostname'] = window.location.hostname;
res['AuthServer'] = 'MaxKey';
return res;
}
......
......@@ -55,7 +55,7 @@ maxkey.app.issuer =CN=ConSec,CN=COM,CN=SH
maxkey.session.timeout =${SERVER_SESSION_TIMEOUT:1800}
maxkey.auth.jwt.issuer =${maxkey.server.uri}
maxkey.auth.jwt.expires =60
maxkey.auth.jwt.expires =600
maxkey.auth.jwt.secret =7heM-14BtxjyKPuH3ITIm7q2-ps5MuBirWCsrrdbzzSAOuSPrbQYiaJ54AeA0uH2XdkYy3hHAkTFIsieGkyqxOJZ_dQzrCbaYISH9rhUZAKYx8tUY0wkE4ArOC6LqHDJarR6UIcMsARakK9U4dhoOPO1cj74XytemI-w6ACYfzRUn_Rn4e-CQMcnD1C56oNEukwalf06xVgXl41h6K8IBEzLVod58y_VfvFn-NGWpNG0fy_Qxng6dg8Dgva2DobvzMN2eejHGLGB-x809MvC4zbG7CKNVlcrzMYDt2Gt2sOVDrt2l9YqJNfgaLFjrOEVw5cuXemGkX1MvHj6TAsbLg
maxkey.auth.jwt.refresh.secret =7heM-14BtxjyKPuH3ITIm7q2-ps5MuBirWCsrrdbzzSAOuSPrbQYiaJ54AeA0uH2XdkYy3hHAkTFIsieGkyqxOJZ_dQzrCbaYISH9rhUZAKYx8tUY0wkE4ArOC6LqHDJarR6UIcMsARakK9U4dhoOPO1cj74XytemI-w6ACYfzRUn_Rn4e-CQMcnD1C56oNEukwalf06xVgXl41h6K8IBEzLVod58y_VfvFn-NGWpNG0fy_Qxng6dg8Dgva2DobvzMN2eejHGLGB-x809MvC4zbG7CKNVlcrzMYDt2Gt2sOVDrt2l9YqJNfgaLFjrOEVw5cuXemGkX1MvHj6TAsbLg
############################################################################
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册