【问题标题】:Basic authentication in REST-applicationREST 应用程序中的基本身份验证
【发布时间】:2013-04-11 11:19:41
【问题描述】:

环境:

  • JAVA
  • 玻璃鱼
  • 不同机器上的 REST 服务
  • HTML5 客户端 AJAX 和 JQuery
  • 球衣

这是我目前实现的:

HTML5-客户端

$('#btnSignIn').click(function () {
    var username = $("#username").val();
    var password = $("#password").val();

    function make_base_auth(user, password) {
        var tok = user + ':' + password;

        var final = "Basic " + $.base64.encode(tok);
        console.log("FINAL---->" + final);
        alert("FINAL---->" + final);

        return final;
    }

    $.ajax({
        type: "GET",
        contentType: "application/json",
        url: "http://localhost:8080/SesameService/webresources/users/secured/login",
        crossDomain: true,
        dataType: "text",
        async: false,
        data: {},
        beforeSend: function (xhr) {
            xhr.setRequestHeader('authorization', make_base_auth(username, password));
        },
        success: function () {
            alert('Thanks for your signin in! ');

        },
        error: function (jqXHR, textStatus, errorThrown) {
            console.log(textStatus, errorThrown);
            alert(' Error in signIn-process!! ' + textStatus);
        }
    });
});

服务器

在安全中,我没有启用安全管理器,它被禁用了!

我已经为 Glassfish 配置了 BASIC-authentication,我的 web.xml 看起来像这样:

<servlet-mapping>
    <servlet-name>ServletAdaptor</servlet-name>
    <url-pattern>/webresources/*</url-pattern>
</servlet-mapping>

<security-constraint>
    <web-resource-collection>
        <web-resource-name>REST Protected resources</web-resource-name>
        <description/>
        <url-pattern>/users/*</url-pattern>
        
    </web-resource-collection>
    <auth-constraint>
        <role-name>admin</role-name>
        <role-name>customer</role-name>
        <role-name>user</role-name>
    </auth-constraint>
</security-constraint>



<login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>jdbcRealm</realm-name>
    </login-config>
    <security-role>
        <role-name>admin</role-name>
    </security-role>
    <security-role>
        <role-name>user</role-name>
    </security-role>
    <security-role>
        <description/>
        <role-name>customer</role-name>
    </security-role>

玻璃鱼

日志

FINE: [Web-Security] Setting Policy Context ID: old = null ctxID = SesameService/SesameService
FINE: [Web-Security] hasUserDataPermission perm: ("javax.security.jacc.WebUserDataPermission" "/webresources/users/secured/login" "GET")
FINE: [Web-Security] hasUserDataPermission isGranted: true
FINE: [Web-Security] Policy Context ID was: SesameService/SesameService
FINE: [Web-Security] hasResource isGranted: true
FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/webresources/users/secured/login" "GET")

问题:

  1. 如果我在用户注册时在客户端加密(不编码)密码并在 SSL/HTTPS 下传输它,这是实现此安全且良好的方法吗?

  2. 如果我在没有客户端的情况下使用 REST 服务,它总是打开的,为什么?没有基本身份验证?我理解这些 url 模式有什么问题吗?

     http://localhost:8080/SesameService/webresources/users/secured/login
    
  3. 如果我得到这个工作如何测试它,因为现在如果我验证一次,我总是被授权?是否可以在 REST 服务中以编程方式“注销”或一般如何实现注销?

  4. 当在带有强制性 base64 编码的用户名:密码的标头中使用授权时,我是否还必须将我的用户名和密码编码到 DB 中?我试过了,并将编码(允许的值是 Hex 和 Base64)添加到 jdbcRealm 到 Glassfish,似乎密码就足够了,但是当两者都在客户端编码时会发生什么?

更新:我更改了 web.xml,现在在浏览器中直接调用 REST 服务时,BASIC-authentication 正在工作:http://localhost:8080/SesameService/users/secured/login

变化:

  • 我在 Glassfish 中启用了安全管理器
  • 我更改了 url 模式
    <servlet-mapping>
        <servlet-name>ServletAdaptor</servlet-name>
        <url-pattern>/*</url-pattern>----> I took webresources off. It was generated by Netbeans
    </servlet-mapping>
  • 我将服务的 url 更改为:http://localhost:8080/SesameService/users/secured/login

现在我在尝试从 HTML5 客户端进行身份验证时收到 HTTP/1.1 401 Unauthorized。

请求标头:

Origin: http://localhost:8383
Host:localhost:8080
Connection:keep-alive
Access-Control-Request-Method:GET
Access-Control-Request-Headers:authorization,content-type

回应:

x-powered-by:Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 3.1.2.2 Java/Oracle Corporation/1.7)
WWW-Authenticate:Basic realm="jdbcRealm"
Server:GlassFish Server Open Source Edition 3.1.2.2
Pragma:No-cache
Expires:Thu, 01 Jan 1970 02:00:00 EET
Date:Sat, 13 Apr 2013 15:25:06 GMT
Content-Type:text/html
Content-Length:1073
Cache-Control:no-cache

更新 2

当我尝试使用 JavaScript + Authorization-header 进行身份验证时,出现 401 错误,并且在日志中:

FINE: [Web-Security] Setting Policy Context ID: old = null ctxID = SesameService/SesameService
FINE: [Web-Security] hasUserDataPermission perm: ("javax.security.jacc.WebUserDataPermission" "/users/secured/login" "OPTIONS")
FINE: [Web-Security] hasUserDataPermission isGranted: true---->!!!!!!!!!!!!!
FINE: [Web-Security] Policy Context ID was: SesameService/SesameService
FINE: [Web-Security] Codesource with Web URL: file:/SesameService/SesameService
FINE: [Web-Security] Checking Web Permission with Principals : null------->!!!!!!!
FINE: [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/users/secured/login" "OPTIONS")
FINEST: JACC Policy Provider: PolicyWrapper.implies, context (SesameService/SesameService)- result was(false) permission (("javax.security.jacc.WebResourcePermission" "/users/secured/login" "OPTIONS"))
FINE: [Web-Security] hasResource isGranted: false------->!!!!!!!!!
FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/users/secured/login" "OPTIONS")
FINEST: JACC Policy Provider: PolicyWrapper.getPermissions(cs), context (null) codesource ((null <no signer certificates>)) permissions: java.security.Permissions@5d4de3b0 (

更新 3 我不能成为第一个也是唯一一个尝试在跨域案例中使用 BASIC 进行身份验证的人。 我像这样更改了我的跨源过滤器:

response.getHttpHeaders().putSingle("Access-Control-Allow-Headers", "Authorization");

不再出现 401 错误,但 JavaScript 中仍然存在错误。在 Glassfish 日志中:

FINEST: JACC Policy Provider:
getPolicy (SesameService/SesameService) is NOT in service----->!!!!!!!!
FINE: JACC Policy Provider: file arrival check type: granted  arrived: false exists: false lastModified: 0 storedTime: 1365968416000 state: deleted SesameService/SesameService
FINE: JACC Policy Provider: file arrival check type: excluded  arrived: false exists: false lastModified: 0 storedTime: 0 state: deleted SesameService/SesameService
FINE: TM: getTransaction: tx=null, tm=null
FINE: TM: componentDestroyedorg.apache.catalina.servlets.DefaultServlet@227fe9a8
FINE: TM: resourceTable before: 0
FINE: TM: resourceTable after: 0

顺便说一句,因为我从来没有做过这项工作,所以这项工作的工作方式与在自己的域中直接调用 REST 服务相同。那么,首先打开客户端请求、服务器请求和用户名密码窗口,然后客户端请求和服务器验证并响应页面?我正在尝试获取它:其中包含授权标头的请求,来自服务器的响应以及来自其余服务的结果,仅此而已。知道如何保护 REST 服务吗?比那更容易?这是不可能的。

更新 4

我只是试图将我的 HTML5 客户端移动到 java web-project 下,只是纯 html 页面和同一域下,BASIC 身份验证工作 100%。所以原因是因为跨域环境。

【问题讨论】:

  • 您是否尝试过关闭并重新打开浏览器?使用 BASIC auth,浏览器会缓存凭据,直到您关闭它 - 它不会不断地反复提示您。不 - 没有办法使用 BASIC 注销,因为凭据是随每个请求一起发送的。
  • 我在 Chrome 和 Firefox 中使用 Private/ignognito-winwows。在任何情况下我什至看不到基本登录窗口,我每次都能进入。我认为问题出在 url-patterns 中,但我不知道为什么以及如何。
  • 我启用了安全管理器,现在它似乎真的在使用它,但是我仍然在没有基本身份验证的情况下进入。:FINEST: JDBCRealm : jaas-context= jdbcRealm, datasource-jndi = sesame , db-user = null, digest-algorithm = none, encoding = null, charset = null INFO: SEC1115: Realm [jdbcRealm] of classtype [com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm] 成功创建。 FINE:已配置领域:jdbcRealm FINE:默认领域设置为:jdbcRealm 信息:SEC1011:安全服务已成功启动 FINE:已安装策略。不会重新安装。
  • 把你的更新放在你的问题的顶部会让你很难(不可能?)理解发生了什么。我什至不知道你最初的问题是从哪里开始的。

标签: java html http-headers authorization basic-authentication


【解决方案1】:

如果我在用户注册时在客户端加密(未编码)密码并在 SSL/HTTPS 下传输,这是实现此功能的安全且好方法吗?

您很可能应该通过 SSL/HTTPS 以纯文本形式简单地传递密码。

SSL/HTTPS 中的所有通信都是加密的,因此加密密码可能不是一个好主意,除非您需要确保 Web 服务器(技术上是 HTTPS 终结器)无法看到密码。

如果我在没有客户端的情况下使用 REST 服务,它总是打开的,为什么?没有基本身份验证?我理解这些 url 模式有什么问题吗?

不确定我是否理解这个问题。然而,BASIC auth 不是 REST 中验证的好模式,因为它以纯文本形式传递密码。

如果我得到这个工作如何测试它,因为现在如果我验证一次,我总是被授权?是否可以在 REST 服务中以编程方式“注销”或一般如何实现注销?

在基本身份验证中,用户名和密码由客户端在每个 HTTP 请求中传递。如果客户端未传递凭据,则服务器拒绝该请求。因此没有会​​话的概念。

但是,就 Java EE 服务器而言,登录会创建一个用户会话,并且同一用户的未来请求将使用同一会话。如果您这样配置,此会话将超时。

如果注销很重要(即控制用户会话),那么您必须为此创建一个 servlet (/logout),这会使 HTTP 会话无效。

标准 Java 安全模型的工作原理如下:当用户登录到安全领域时,Java EE 服务器会在您的浏览器中存储一个安全 cookie。浏览器在对同一领域的每个请求中将此 cookie 发送回 Java EE 服务器。 Java EE 服务器在每个请求中检查此 cookie 并使用它来识别用户,将请求连接到用户的会话。

因此,您可能希望 REST 服务与 Web 应用程序处于相同的安全领域,以便浏览器和服务器无缝工作。

当在带有强制性 base64 编码的用户名:密码的标头中使用授权时,我是否还必须将我的用户名和密码编码到 DB 中?我试过了,并将编码(允许的值是 Hex 和 Base64)添加到 jdbcRealm 到 Glassfish,似乎密码就足够了,但是当两者都在客户端编码时会发生什么?

不,不要对用户名或密码进行编码 - 这一切都在浏览器和 Glassfish 内部处理。如果你在客户端编码,客户端会对编码进行编码,服务器会拒绝密码。


您能否告诉我是否可以将 html5-page 中的 j_security_check 与 javaScript 一起使用,或者我是否再次遇到问题 :) 我已经制作了几个 Primefaces + jsf-application 我使用了 FORM-auth 并且没有任何问题,但这对我来说完全是灾难性的案例。

假设 RESTful 服务位于相同的域和安全领域(然后登录到 Web 应用程序将允许浏览器将正确的 cookie 发送到 REST URI),您应该能够使用 j_security_check 轻松地完成这项工作。

但请注意,其他应用程序将难以访问 REST 服务。基本上,他们必须通过 j_security_check 登录,然后维护 Glassfish 发送的 cookie。如果您确实需要其他应用程序以编程方式访问这些服务,那么您将需要另一个解决方案:

  1. 您可以设置安全域以允许不同的身份验证器“足够”;设置 HTTP BASIC Auth以及并确保没有标记为“必要”
  2. 两次部署 RESTful 服务,另一个是不同的 URI。如果您想使用 HTTP BASIC Auth,这可能是 SSL/HTTPS 端点,以确保安全处理密码

【讨论】:

  • >When the user logs in to a security realm, the Java EE server stores a secure cookie in your browser - 情况并非如此。用户在 Java EE 服务器上创建与特定 Web 应用程序的会话,并为此在响应中放置一个 cookie。安全领域是否恰好与该 Web 应用程序相关联在很大程度上与此会话无关。仅当在 web.xml 中如此配置时才使用安全 cookie。 Java EE 没有详细说明身份验证模块应如何记住身份验证。在 Java EE 7 (JASPIC 1.1) 之前,它甚至根本没有提到记忆。
  • @ArjanTijms:我听到了;我试图简化实际发生的事情(以及在实践中在 JEE 7、6、5、...中发生的事情)。 Sami 的关键是客户端上有一个会话。
  • 我将我的 html5-client 代码移动到同一个项目中,其中其余服务位于同一个域下,并且工作正常。所以跨域可能是罪魁祸首。这是非常烦人的情况,我可能会尝试在唯一的域下使用 javascript + j_security_check 实现普通的 FORM-auth。顺便说一句,在这种情况下可以使用 j_security_check 吗?
  • @Sami - 我现在正在打电话。跨域几乎肯定是根本原因。这是因为服务器需要防止跨域脚本攻击,并且您的 REST 服务看起来完全像这样。几个小时后我会再写一些
  • @Andrew Alcock。谢谢!你能告诉我是否可以将 html5-page 中的 j_security_check 与 javaScript 一起使用,还是我又遇到问题了:) 我已经制作了几个 Primefaces + jsf-application 我使用了 FORM-auth 并且没有任何问题,但是这对我来说完全是一场灾难。
【解决方案2】:



我正在开发带有 Spring Security 的 Spring MVC 框架并使用基本身份验证:

基本上,在 HTTP 基本身份验证中,用户名和密码在 Base64 类(来自 util 包)的帮助下转换为密钥或访问令牌。

并且该密钥被设置到 HTTP URL 的标头中,然后命中服务器。

它在服务器上工作正常:通过相同的 Base64 解码用户名密码密钥,然后与数据库用户名和密码匹配。如果身份验证完成,则进一步处理。

这提供了 API Urls 的安全性。每个人都不容易接近。

代码: 创建访问令牌(key)的代码是:

注意:我们在 Base64 中传递字符串(来自 util 包)“YourUsername:YourPassword”进行编码。
是的,用户名和密码用“:”分隔。

        String encodeddata = new String(Base64.encodeBase64("username:password".getBytes()));
        System.out.println("encodedBytes " + encodeddata);

设置key到header的代码:

//使用密钥请求 URL

        HttpClient client = HttpClientBuilder.create().build();
        HttpGet get = new HttpGet("http://google.com/Abc/api/vou/redeemed");
        get.setHeader("Authorization", "Basic "+encodeddata); //key getting above

注意:您可以将它与 Get 和 Post 方法一起使用。这里我只使用 get 方法。 //响应

        HttpResponse jresponse = client.execute(get);

        System.out.println("Response Code : "+ jresponse.getStatusLine().getStatusCode());

        BufferedReader rd = new BufferedReader(new InputStreamReader(jresponse.getEntity().getContent()));

        StringBuffer result = new StringBuffer();
        String line = "";
        while ((line = rd.readLine()) != null) {
            result.append(line);
        }

        System.out.println("------Response is ::: "+ result);

//解码键

        String usernameAndPassword = null;
        try {
            usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword));
        } catch (Exception e) {
            System.out.println("SERVER_ERROR");
        }
        System.out.println(usernameAndPassword);

        final StringTokenizer tokenizer = new StringTokenizer(encodedUserPassword, ":");
        final String username = tokenizer.nextToken();
        final String password = tokenizer.nextToken();

        System.out.println(username);
        System.out.println(password);

【讨论】:

    猜你喜欢
    • 2014-01-26
    • 2016-07-30
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 2016-05-04
    • 2013-06-06
    • 2016-03-20
    • 1970-01-01
    相关资源
    最近更新 更多