Keycloak Oauth2.0流程 --- Angular前端 + Django后端 + Keycloak 实现SSO功能

目录

前言:

前端Angular集成Keycloak:

1.使用github开源的的库 keycloak-angular:

安装Keycloak Angular和keycloak客户端keycloak-js库:

Angular中设置Keycloak:

Keycloak登录:

2.自己实现集成Keycloak功能 -- 实现Oauth2.0流程 :

(1).实现Keycloak登录:

(2).登录成功,获取Keycloak相关信息:

3.关于Nginx配置:

后端Django集成Keycloak:

1.实现中间件:

2.实现Keycloak的API工具类:

代码地址:


前言:

    一个企业往往拥有多个应用系统,如果每个应用都有独立的用户认证和授权管理功能,这不仅需要运维人员维护多套用户管理系统,用户使用每个系统时都要登录一次,非常不方便。如果能够将所有应用系统的用户集中管理,用户只登录一次,就可以无需再次登录而访问所有系统,这将会大大改善用户体验。本文前端Angular, 后端Django,基于Keycloak搭建单点登录系统。所以代码均经过验证,直接从工程代码中拷贝。

前端代码部署到nginx, 前后端代码分开部署

前端Angular集成Keycloak:

本文使用前端登录,而不是后端登录的方式。后端只负责检测前端发送的请求携带的access token是否可以访问Keycloak,以及访问Keycloak server的几个API。

前端要怎样集成Keycloak呢? 本文提供两种方式

1.使用github开源的的库 keycloak-angular:

原始的keycloak js adapter:前端集成Keycloak,根据官方文档,keycloak提供了一个JavaScript adapter,详情请参考:https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter

这个keycloak-angular库对keycloak提供的这个JavaScript adapter进行了包装,可以帮助用户在Angular应用中更方便的使用,  请参考github:GitHub - mauriciovigolo/keycloak-angular: Easy Keycloak setup for Angular applications. 它提供了以下特性:

  • 提供了一个keycloak服务,它包装了要在Angular中使用的keycloak-js函数,为原始函数(keycloak-js文件中提供的函数)提供了额外的功能,并且还添加了新的函数,使其更容易被Angular应用程序使用
  • 通用的AuthGuard实现,用户可以通过继承其认证逻辑和角色加载,自定义所需要的AuthGuard逻辑。
  • 提供了一个HttpClient拦截器,登录成功之后,每个http请求都会在header中增加认证信息(其实就是登录成功之后的keycloak access token),用户可以禁用这个拦截器,也可以设置排除某个url的http请求。下面是拦截器源码中,给http请求的header中增加access token的代码:
  /**
   * Adds the token of the current user to the Authorization header
   *
   * @param req
   * @param next
   */
  private handleRequestWithTokenHeader(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<any> {
    return this.keycloak.addTokenToHeader(req.headers).pipe(
      mergeMap(headersWithBearer => {
        const kcReq = req.clone({ headers: headersWithBearer });
        return next.handle(kcReq);
      })
    );
  }

安装Keycloak Angular和keycloak客户端keycloak-js库:

具体版本请根据自己的angular版本和keycloak版本进行设置。

npm install keycloak-angular keycloak-js

本文所用版本:

当然,如果用户想用自己keycloak服务器中的keycloak-js,可以选择不安装keycloak-js库,而是在index.html入口文件中直接下载keycloak server中的keycloak.js文件。

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Conduit</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="//code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css">
  <link href="//fonts.googleapis.com/css?family=Titillium+Web:700|Source+Serif+Pro:400,700|Merriweather+Sans:400,700|Source+Sans+Pro:400,300,600,700,300italic,400italic,600italic,700italic" rel="stylesheet" type="text/css">
  <link rel="stylesheet" href="//demo.productionready.io/main.css">

  <!-- 导入keycloak服务器的keycloak.js脚本文件 -->
  <script src="http://127.0.0.1:8082/auth/js/keycloak.js" type="text/javascript"></script>
</head>

Angular中设置Keycloak:

为了确保Keycloak在Angular应用启动时被初始化,必须在AppModule中添加一个APP_INITIALIZER provider。这个provider将调用如下所示的initializeKeycloak工厂函数,该函数将设置Keycloak服务,以便在应用程序中使用它。

keycloak-init.ts

Note0: 这里我们把client_secret( 下面代码中的credentials: )的值设置到了前端,用于在登录成功后,前端利用返回的授权码code和我们这里设置的client_secret直接访问keycloak API去获取access token;而不是将client_secret保存到后端,前端发送http请求,将授权码code发送到后端,后端利用授权码code和已经保存配置好的client_secret去调用keycloak的API去获取access token,并返回给前端,第2章节就是用的这种方式,哪种方式更安全呢? 我认为第2章节的方式更安全

import { KeycloakService } from 'keycloak-angular';

export function initializeKeycloak(keycloak: KeycloakService) {
  return () =>
    keycloak.init({
        config: {
            realm: "master",//设置Realm
            url: '/auth', //程序部署到nginx上,该请求将被反向代理到keycloak server。
            clientId: 'frontend-client', //设置client
            credentials : {secret : "8f28abe0-f4de-476d-bd8b-5354d78639de"}//设置client credentials
        },
        initOptions: {
            onLoad: 'login-required',
            flow: 'standard'
        },
    }).then(() => {
        //登录成功
        var roleId = "0";
        var roleTypes = {"ROLE_ADMIN": "1","ROLE_VIEWER": "2", "ROLE_OPERATOR": "3"};
        var roleArray = keycloak.getUserRoles(true); //登录成功,获取当前登录用户的role信息
        //登录成功后,将role信息保存到sessionStorage,以后整个应该中随时随处可以使用
        if(roleArray.length == 0){
          sessionStorage.setItem('userRole', "3");
        }else{
          roleArray.indexOf("ADMIN_ROLE") != -1 ? sessionStorage.setItem('userRole', "1"): roleArray.indexOf("OPERATOR_ROLE") != -1 ? sessionStorage.setItem('userRole', "2"): sessionStorage.setItem('userRole', "3")
        }
        
        console.log("Debug: the current user name is: " + keycloak.getUsername(),)
        //得到keycloak 的 access token。
        keycloak.getToken().then(data => {
          console.log("Debug: the keycloak access token is " + data);
          sessionStorage.setItem('accessToken', data);
        })
    }).catch((error) =>
        //登录失败
        console.error('Keycloak login failed: ', error)
    );

}

在AppModule中添加APP_INITIALIZER provider:

import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular';
import { initializeKeycloak } from './core/services/keycloak-init'; //导入initializeKeycloak工厂函数

@NgModule({
  declarations: [AppComponent, FooterComponent, HeaderComponent],
  imports: [
    BrowserModule,
    CoreModule,
    SharedModule,
    HomeModule,
    AuthModule,
    AppRoutingModule,
    KeycloakAngularModule
  ],
  providers: [
    //添加APP_INITIALIZER provider
    {
      provide: APP_INITIALIZER,
      useFactory: initializeKeycloak,
      multi: true,
      deps: [KeycloakService],
    },
  ],
  bootstrap: [AppComponent]
})

Keycloak登录:

Keycloak设置完成之后,启动Angular应用,浏览器中输入 https://localhost/ ,自动重定向到keycloak登录页面。

登录成功之后,每次向后端发送请求,发现header中加入了keycloak access token信息(keycloak-angular提供的HttpClient拦截器会加入access token)。

  getBackendInfo(){
    this.brucetest = JSON.stringify({"hello":"bruce"});
    this.http.get(`/api/adminresource/`).subscribe(
      response => {
        console.log("Debug: the value of res is " + JSON.stringify(response));
        this.brucetest = JSON.stringify(response);
      },
      err => {
        console.log("Debug: error message" + err);
      }
    )
  }

2.自己实现集成Keycloak功能 -- 实现Oauth2.0流程 :

如果担心开源的keycloak-angular库不安全或者有隐患,那么可以自己实现前端keycloak登录功能。

参考视频:【IT老齐211】说人话讲明白OAuth2经典授权码模式_哔哩哔哩_bilibili

本文中Angular应用启动后,自动路由到login组件,我们在login组件中集成keycloak功能(用户自便,什么组件都行)。

首先判断是否登录了keycloak,如果已经登录的话,cookie中会有一个key,叫做"code"(Oauth2.0协议中,授权码模式中的授权码)。如果有这个code,说明用户已经登录成功了(用户登录成功后,会重定向到我们自己的angular页面 redirect_url,重定向地址中带着这个keycloak给我们返回的授权码code

getCookie(cname) {
  var name = cname + "=";
  var ca = document.cookie.split(';');
  for (var i = 0; i < ca.length; i++) {
    if (ca[i].indexOf(name) >= 0) {
      console.log(ca[i].slice(ca[i].indexOf(name)));
      return ca[i].slice(ca[i].indexOf(name) + 5)
    }
  }
  return "";
}
this.code = this.getCookie('code');

(1).实现Keycloak登录:

然后判断code的值是否为空,如果为空并且程序中没有保存过keycloak access token,说明没有登录(既没有code授权码,又没有access token),则重定向到keycloak登录地址进行登录;如果不为空,说明登录了,获取keycloak 的access token, refresh token等信息。

if (this.code == '') {
  this.checkAccessToken();
} else {
  this.getAccessToken();
}

//如果code为空,则判断是否保存过keycloak access token,如果没有,则说明没有登录过,需要登录keycloak。
checkAccessToken() {
  if (!this.getUser().accessToken) { this.getUser()是自己实现的一个函数,用来访问sessionStorage, 请自行实现,非常简单。
    this.redirectKeycloakSSO();
  } else {
    //已经登录,直接跳转到应用的主界面。
    this.router.navigateByUrl('/home');
  }
}
//重定向到keycloak登录页面。
redirectKeycloakSSO() {
    let url = "/auth/realms/" + this.keycloakRealm + "/protocol/openid-connect/auth";
    let params = "redirect_uri=" + this.redirect_uri + "/&client_id=" + this.client_id + "&response_type=" + this.response_type + "&scope=" + this.scope;
    window.location.href = url + "?" + params;
}

 Note1: 跳转到登录页面的时候,使用到了参数client_id,这个参数都是在keycloak中注册client的时候自动生成给我们的;redirct_url也是在注册client的时候,我们手动填写的。

(2).登录成功,获取Keycloak相关信息:

在keycloak登录页面登录成功后,会自动跳转到我们的angular前端页面(配置keycloak client时配置的redirect_url,并且在这个地址上附带了code授权码),还是路由到login组件,在该组件中会重新执行上面的代码,这次有了code(授权码)将会执行this.getAccessToken(),使用cookie中的code值,访问Django后端,后端去访问keycloak,获取keycloak的access token, refresh token等

Note2:这次会用到client_secret,这个参数也是在keycloak中注册client的时候自动生成给我们的。并且这个client_secret一定要保存到我们自己系统的后端,不能泄漏给前端,这是唯一保证我们用户信息不泄露的保障 ----- 重要 重要 重要,这也是为什么获取access token要调用后端Django API的原因,即使前端的授权码code泄漏了,只要获取不到我们的client_secret,那么就不会有任何的泄漏风险。

Note3:keycloak的登录过程和登录后获取access token的整个流程就是实现了Oauth2.0的授权码模式。

//登录后使用cookie中的code值获取keycloak的access token, refresh token等信息。
getAccessToken() {
  let data = {
    "redirect_uri": this.redirect_uri + "/",
    "code": this.code,
  }
  jQuery.ajax({
    url: this.csfWidgets.backendUrl + "/api/keycloak/token/",
    data: JSON.stringify(data),
    type: "POST",
    async: false,
    contentType: 'application/json',
    complete: (xhr) => {
    },
    success: (data, status, xhr) => {
      document.cookie = 'params=0;expires=' + new Date(0).toUTCString();
      if(! data.hasOwnProperty("access_token")){
        console.log('Error: Can not get keycloak access token!');
        return;
      }
      
      this.getUserInfo(data);
    },
    error: (data, status, xhr) => {
      console.log("Failed: " + JSON.parse(data.responseText).resMsg);
    },
    fail: (data, status, xhr) => {
      console.log("Failed: " + JSON.parse(data.responseText).resMsg);
    }
  });
}

下面只简单贴一下后端Django程序收到前端发送的request后,是怎样利用code和redirect_uri信息取得keycloak的access token,refresh token等信息。这个get_refresh_token函数肯定是在View中被调用,此处不贴代码了,太啰嗦。

def get_refresh_token(self, code, redirect_uri):
    payload = {
        "client_id": self.client_id,
        "code": code,
        "redirect_uri": redirect_uri,
        "grant_type": 'authorization_code',
        "client_secret": self.client_secret_key
    }
    #keycloak server的API。
    URL = '%s/realms/%s/protocol/openid-connect/token' % (self.server_url,self.realm)
    # sending get request and saving the response as response object
    try:
        #访问keycloak server,获取access token, refresh token等信息。
        #reponse = requests.get(url = URL, headers=self.headers,verify=False,proxies=self.proxies)
        response = requests.post(url = URL, data=payload,verify=False)
        responseData = ""
        if response.status_code > 201:
            if response.status_code == 403:
                responseData = {"res_status":403,"detail":"This user does not have permission to get keycloak refresh token from keycloak."}
            elif response.status_code == 401:
                responseData = {"res_status":401,"detail": str(response.text)}
            else:
                responseData = {"res_status":response.status_code,"detail":"Can't get access and refresh token from keycloak server, " + str(response.text)}
            return responseData
        elif response.headers.get('Content-Type') is not None and 'json' in response.headers.get('Content-Type'):
            # extracting data in json format
            data = response.json()
            return data
        else:
            responseData = {"res_status":response.status_code,"detail":"Can't get access and refresh token from keycloak server, " + str(response.text)}
            return responseData
    except Exception as e:
        print("Debug: Can't get keycloak refresh token because of: " + str(e))
        self.logger.error("Exception found when try to get refresh token from keycloak: " + str(e))
        if type(e) == requests.exceptions.ConnectionError :
            return {"res_status":500,"detail":"ConnectionError, can't connect the keycloak server."}
        return {"res_status":500,"detail":"Can't get keycloak refresh token."}

前端获取到response后,解析response信息,从中获得access token, refresh token等信息,并保存到sessionStorage中,解析access token 得到role信息,并且保存到sessionStorage中,以上操作完成后,认证过程全部完成,跳转到主界面 this.router.navigateByUrl('/home');

//解析response信息,参数data是传入的response信息
getUserInfo(data) {
  var tokenInfo = this.decodeToken(data.access_token);
  var roleId = "0";
  var roleArray = tokenInfo['realm_access']['roles'];
  //console.log("Debug: the current user roles is " + roleArray);
  if(roleArray.length == 0){
    roleId = '3';
  }else{
    roleArray.indexOf("ADMIN_ROLE") != -1 ? sessionStorage.setItem('userRole', "1"): roleArray.indexOf("OPERATOR_ROLE") != -1 ? sessionStorage.setItem('userRole', "2"): sessionStorage.setItem('userRole', "3");
  }
  const userinfo = JSON.parse(sessionStorage.getItem('user'));
  const user = userinfo ? userinfo : {};
  Object.assign(user, {
    username: tokenInfo['preferred_username'],
    roleId: roleId,
    passwordFlag: false, //Don't use original method to change the admin user password.
  });

  // this.role = data.roles;
  const keycloakAuthInfo = {
    "accessToken": data.access_token,
    "refreshToken": data.refresh_token
  };
  //将信息保存到sessionStorage中。
  this.updateUser(keycloakAuthInfo);
  this.updateUser(user);
  //将access token, refresh token信息保存到cookie中,这样以后每次向后端发送http请求,都会自动带上access token信息。
  this.csfWidgets.setCookie("keycloakToken=" + data.access_token);
  //所以认证授权工作完成之后,跳转到主界面
  this.router.navigateByUrl('/home');
}

至此,所有的认证工作均已完成,系统可以在keycloak保护下正常工作了,前端发送的每个http请求,都会带上access token信息(cookie中设置了access token,所有http请求都会带上这个access token,为了防止CSRF跨站请求伪造攻击,把这个access token放到每个http请求的header中,例如Authorization这个自己命名的header,后端也要校验这个header,不要校验cookie中的access token),后端Django中间件会检查这个access token是否有效,如果有效,则执行业务代码,正常工作;如果失效(例如过期),则返回401错误,让用户重新登录。

Note3: keycloak的登录过程和登录后获取access token的整个流程就是实现了Oauth2.0的授权码模式。 

3.关于Nginx配置:

前端在发送登录请求的时候,nginx会通过反向代理将请求转发到keycloak server,从而重定向到keycloak登录页面,登录后再获取access token,但此时token中的issuer地址是nginx地址,后端使用这个access token访问keycloak server的时候,会要求issuer地址是keycloak server地址,而不是nginx地址,这就需要nginx的反向代理来解决,具体请参考:通过nginx访问keycloak时的Invalid token issuer问题_知难行难1985的博客-CSDN博客_keycloak nginx

后端Django集成Keycloak:

前面大体介绍了一下整个keycloak认证的整个过程,包括前端和后端的流程。下面详细介绍后端的实现。

主要是需要实现一个中间件,作用是拦截来自Angular前端的每个请求,进行认证检查。从request的Header获取keycloak access token,利用这个token去访问keycloak server,如果能够成功,说明用户登录了,中间件返回"None",继续执行View,进行业务处理;如果失败,说明用户没有登录,中间件直接返回HttpResponse,请求完毕,不再继续进行业务处理。

下图是中间件工作原理:

1.实现中间件:

在settings.py中配置Keycloak信息:

KEYCLOAK_CONFIG = {
    'KEYCLOAK_SERVER_URL': 'https://127.0.0.1:8443/auth',
    #跟前端保持一致,使用相同的Realm和client
    'KEYCLOAK_REALM': 'master',
    'KEYCLOAK_CLIENT_ID': 'frontend-client', 
    'KEYCLOAK_CLIENT_SECRET_KEY': '8f28abe0-f4de-476d-bd8b-5354d78639de',
    'KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS':[r'api/keycloak/token/*', r'api/keycloak/config/*',r'api/automation/token/*',r'api/keycloak/logout/'] #python Regular expression
}

其中配置在"KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS"中的url,中间件不会进行用户认证检查,例如使用code获取access token信息的"api/keycloak/token/", 因为是前端登录之后第一次访问后端,此时还未获得access token信息,所以不进行认证检查。

中间件的具体实现:具体细节看代码注释

class KeycloakMiddleware(MiddlewareMixin):

    def __init__(self, get_response):
        """
        :param get_response:
        """

        self.config = settings.KEYCLOAK_CONFIG

        # Read configurations
        try:
            self.server_url = self.config['KEYCLOAK_SERVER_URL']
            self.client_id = self.config['KEYCLOAK_CLIENT_ID']
            self.realm = self.config['KEYCLOAK_REALM']
        except KeyError as e:
            raise Exception("KEYCLOAK_SERVER_URL, KEYCLOAK_CLIENT_ID or KEYCLOAK_REALM not found.")

        self.client_secret_key = self.config.get('KEYCLOAK_CLIENT_SECRET_KEY', None)
        self.client_public_key = self.config.get('KEYCLOAK_CLIENT_PUBLIC_KEY', None)
        self.default_access = self.config.get('KEYCLOAK_DEFAULT_ACCESS', "DENY")
        self.method_validate_token = self.config.get('KEYCLOAK_METHOD_VALIDATE_TOKEN', "INTROSPECT")
        self.keycloak_authorization_config = self.config.get('KEYCLOAK_AUTHORIZATION_CONFIG', None)
        self.keycloak_bearer_authentication_exempts = self.config.get('KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS', None)

        # Django
        self.get_response = get_response

        #lock
        self.lock = threading.Lock()
     .........................................................

    def process_view(self, request, view_func, view_args, view_kwargs):
        """
        Validate only the token introspect.
        :param request: django request
        :param view_func:
        :param view_args: view args
        :param view_kwargs: view kwargs
        :return:
        """
        #Check if this request path is no authentication required, if yes, go to views.
        if len(self.keycloak_bearer_authentication_exempts) > 0:
            path = request.path_info.lstrip('/')
            print("Debug: the url path is " + path)
            if any(re.match(m, path) for m in
                   self.keycloak_bearer_authentication_exempts):
                logger.debug('** exclude path found, skipping')
                return None
        
        accessToken = "";
        #如果Header中没有token信息'HTTP_AUTHORIZATION',则判断'HTTP_COOKIE'中有没有
        if 'HTTP_AUTHORIZATION' not in request.META:
            if not request.META.get('HTTP_COOKIE'):
                print("Debug: can't get the cookie keycloak token");
                return JsonResponse({"detail": NotAuthenticated.default_detail},
                    status=NotAuthenticated.status_code)
            else:
                #得到token信息
                accessToken =request.COOKIES["keycloakToken"];
                print("Debug: the request cookie token is: " + accessToken);
        #如果Header中有token信息'HTTP_AUTHORIZATION',则从'HTTP_AUTHORIZATION'中获取token。
        if not accessToken.strip():
            auth_header = request.META.get('HTTP_AUTHORIZATION').split()
            accessToken = auth_header[1] if len(auth_header) == 2 else auth_header[0]
            print("Debug: the request token is " + accessToken);

        try:
            #使用keycloak API工具类KeycloakHttpRequest,访问keycloak server获取用户信息
            keycloakHttpRequest = KeycloakHttpRequest(self.server_url,self.client_secret_key,self.realm,self.client_id)
            #访问keycloak server,得到登录用户信息
            keycloakUserInfo = keycloakHttpRequest.get_userinfo(accessToken)
            print("Debug: the keycloakUserInfo is: " + json.dumps(keycloakUserInfo))
            keycloakUserRoles=''
            #print("Debug: the userInfoData username is %s, user ID is %s " % (userinfo['preferred_username'],userinfo['sub']))
            if keycloakUserInfo is not None: 
                if "res_status" in keycloakUserInfo:
                    return JsonResponse({"detail": keycloakUserInfo['detail']},status=keycloakUserInfo['res_status'])
                
                if 'user_roles' in keycloakUserInfo:
                    # 从用户信息中获取role信息。
                    keycloakUserRoles = keycloakUserInfo['user_roles']
                    request.META['USER_ROLES'] = ','.join(keycloakUserRoles)
                else:
                    #Get user roles(User must have the Client Roles 'view-users', otherwise there will be exception.)
                    print("Debug: current user is: " + keycloakUserInfo['sub'])
                    //如果用户信息keycloakUserInfo中没有role信息,则直接访问keycloak server获取
                    roles = keycloakHttpRequest.get_user_roles(keycloakUserInfo['sub'],accessToken)
                    if "res_status" in roles:
                        return JsonResponse({"detail": roles['detail']},status=roles['res_status'])
                    if roles is None or not len(roles):
                        keycloakUserRoles = None
                        request.META['USER_ROLES'] = 'VIEWE_ROLE'
                    else:
                        keycloakUserRoles = [user['name'] for user in roles]
                        request.META['USER_ROLES'] = ','.join(keycloakUserRoles)
                        

在keycloak中间件实现之后, 将其加入到settings.py中的MIDDLEWARE列表中.

2.实现Keycloak的API工具类:

工具类KeycloakHttpRequest.py调用各种API(带上access token)访问keycloak server,获取需要的信息,例如获取用户信息,得到用户role信息,使用refresh token刷新access token,keycloak登出等等。此工具类是供中间件(例如 获取用户信息)和View(例如 keycloak登出)使用的。

class KeycloakHttpRequest(object):
    def __init__(self,server_url,client_secret_key,realm='master',client_id='admin-cli'):
        if server_url.endswith('/'):
            self.server_url = server_url[:-1]
        else:
            self.server_url = server_url
        self.realm = realm
        self.client_id = client_id
        self.client_secret_key = client_secret_key
        self.proxies={
            'http':'http://cnproxy.int.nokia-sbell.com/proxy.pac',
            'https':'http://cnproxy.int.nokia-sbell.com/proxy.pac'
        }

    def get_userinfo(self, accessToken):
        headers={
            'Authorization': 'Bearer ' + accessToken,
            'Content-Type': 'application/json'
        }
        #http://$KC_SERVER/$KC_CONTEXT/realms/$KC_REALM/protocol/openid-connect/userinfo"
        userInfoUrl = '%s/realms/%s/protocol/openid-connect/userinfo' % (self.server_url,self.realm)
        try:
            #userInfoResponse = requests.get(url = userInfoUrl, headers=self.headers,verify=False,proxies=self.proxies)
            userInfoResponse = requests.get(url = userInfoUrl, headers=headers,verify=False)
            userInfoData=''
            print("debug: the userinfo reponse info is : " + str(userInfoResponse))
            if userInfoResponse.headers.get('Content-Type') is not None and 'json' in userInfoResponse.headers.get('Content-Type'):
                userInfoData = userInfoResponse.json()
            else:
                if userInfoResponse.status_code == 403:
                    userInfoData = {"res_status":403,"detail":"This user does not have permission to access the user info from keycloak."}
                elif userInfoResponse.status_code == 401:
                    userInfoData = {"res_status":401,"detail": str(userInfoResponse)}
                else:
                    userInfoData = {"res_status":userInfoResponse.status_code,"detail":"Can't get the user info from keycloak server."}
            return userInfoData
        except Exception as e:
            print("Debug: the error message: " + str(e))
            print("Debug: the type of Exception : " + str(type(e)))
            if type(e) == requests.exceptions.ConnectionError :
                return {"res_status":500,"detail":"ConnectionError, can't connect the keycloak server."}
            return {"res_status":401,"detail":"Can't get the user info from keycloak server."}

    def get_user_roles(self,user_uuid, accessToken):
        headers={
            'Authorization': 'Bearer ' + accessToken,
            'Content-Type': 'application/json'
        }
        #/auth/admin/realms/{realm}/users/{user-uuid}/role-mappings/realm
        URL = '%s/admin/realms/%s/users/%s/role-mappings/realm' % (self.server_url,self.realm,user_uuid)
        # sending get request and saving the response as response object 
        try:
            # print("Debug: try to get user roles from keycloak server " + URL)
            #reponse = requests.get(url = URL, headers=self.headers,verify=False,proxies=self.proxies)
            response = requests.get(url = URL, headers=headers,verify=False)
            print("Debug: user roles reponse string is:" + str(response.headers))
            print("Debug: user roles reponse string is:" + str(response.headers.get('Content-Type')))
            userRoleData=''
            if response.headers.get('Content-Type') is not None and 'json' in response.headers.get('Content-Type'):
                # extracting data in json format 
                # print("Debug: user roles reponse string is:" + str(response))
                data = response.json()
                # print("Debug: the user roles info is " + str(data))
                return data
            else:
                if response.status_code == 403:
                    userRoleData = {"res_status":403,"detail":"This user does not have permission to get the user role info from keycloak."}
                elif response.status_code == 401:
                    userRoleData = {"res_status":401,"detail": str(response)}
                else:
                    userRoleData = {"res_status":response.status_code,"detail":"Can't get the user role info from keycloak server."}
                return userRoleData
        except Exception as e:
            print("Debug: the error message: " + str(e))
            print("Debug: the type of Exception : " + str(type(e)))
            if type(e) == requests.exceptions.ConnectionError :
                return {"res_status":500,"detail":"ConnectionError, can't connect the keycloak server."}
            return {"res_status":500,"detail":"Can't get the user role info from keycloak server."}

代码地址:

本文代码github地址GitHub - wdquan1985/Angular-Django-Keycloak

前端集成keycloak使用方式一,使用开源的keycloak-angular库。用户可以根据上面提供的第二种方式自己实现集成keycloak。前端提供的主界面及其简单,因为完全没必要加入其它任何业务,只是完成keycloak登录后,进入主界面,发送一个http请求,然后在主界面将response显示出来就好。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 前后端分离是一种将前端界面与后端逻辑进行分离开发的架构方式,使得前端后端可以并行开发。OAuth 2.0是一种授权框架,用于授权和认证流程的规范化,而Spring Security是一个在Java中实现安全控制的框架,提供了大量的安全特性。Spring Authorization Server是Spring Security中用于实现授权服务器的模块,它支持OAuth 2.0的各种授权模式。 密码模式是OAuth 2.0中的一种授权模式,它允许用户通过提交用户名和密码来获取访问令牌,然后使用该令牌来访问受保护的资源。在前后端分离的架构中,可以使用Spring Security配合Spring Authorization Server来实现密码模式的认证和授权。 在密码模式下,前端首先需要收集用户的用户名和密码,并将其发送给后端后端使用Spring Security提供的密码编码器对密码进行加密,并验证用户名和密码的正确性。如果验证通过,则后端向客户端颁发一个访问令牌,通常是一个JWT(JSON Web Token)。前端使用获得的访问令牌来访问需要受保护的资源,每次请求将该令牌作为Authorization头的Bearer字段发送给后端进行验证。后端可以使用Spring Security的资源服务器来验证该令牌的有效性,并根据用户的权限控制对资源的访问。 使用Spring Security和Spring Authorization Server的密码模式可以实现安全的前后端分离架构。通过合理配置和使用安全特性,可以保障用户的身份认证和资源的授权,确保系统的安全性。 ### 回答2: 前后端分离是一种软件架构模式,前端后端通过使用API进行通信,分别负责处理用户界面和数据逻辑。OAuth 2.0是一种用于授权的开放标准协议,它允许用户在第三方应用程序中授权访问其受保护的资源。Spring Security是Spring框架中的一个模块,提供了身份验证和授权功能。 在前后端分离的架构中,前端应用程序通常需要使用OAuth 2.0协议进行用户授权,以访问后端应用程序的受保护资源。为了实现密码模式,我们可以使用Spring Security的模块之一,即spring-authorization-server。 spring-authorization-server是Spring Security的一个子模块,用于实现OAuth 2.0协议中的授权服务器。密码模式是OAuth 2.0协议中的一种授权模式,允许前端应用程序通过用户的用户名和密码进行授权。密码模式在安全性上有一定的风险,因此在实际应用中需要谨慎使用。 使用spring-authorization-server的密码模式,我们可以在前端应用程序中收集用户的用户名和密码,并将其提交给后端应用程序进行验证。后端应用程序将使用Spring Security进行身份验证,并向前端应用程序颁发一个访问令牌,该令牌可以用于后续的API请求。 通过使用前后端分离、OAuth 2.0和spring-authorization-server的密码模式,我们可以实现安全的用户授权和身份验证机制,确保只有经过授权的用户才能访问受保护的资源。这种架构模式能够提高系统的安全性和可扩展性,适用于各种类型的应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值