1 /* 2 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.net.httpserver; 27 28 import java.nio.charset.Charset; 29 import java.util.Base64; 30 import java.util.Objects; 31 32 import static java.nio.charset.StandardCharsets.UTF_8; 33 34 /** 35 * BasicAuthenticator provides an implementation of HTTP Basic 36 * authentication. It is an abstract class and must be extended 37 * to provide an implementation of {@link #checkCredentials(String,String)} 38 * which is called to verify each incoming request. 39 */ 40 public abstract class BasicAuthenticator extends Authenticator { 41 42 protected final String realm; 43 private final Charset charset; 44 private final boolean isUTF8; 45 46 /** 47 * Creates a BasicAuthenticator for the given HTTP realm. 48 * The Basic authentication credentials (username and password) are decoded 49 * using the platform's {@link Charset#defaultCharset() default character set}. 50 * 51 * @param realm The HTTP Basic authentication realm 52 * @throws NullPointerException if realm is {@code null} 53 * @throws IllegalArgumentException if realm is an empty string 54 */ BasicAuthenticator(String realm)55 public BasicAuthenticator (String realm) { 56 this(realm, Charset.defaultCharset()); 57 } 58 59 /** 60 * Creates a BasicAuthenticator for the given HTTP realm and using the 61 * given {@link Charset} to decode the Basic authentication credentials 62 * (username and password). 63 * 64 * @apiNote {@code UTF-8} is the recommended charset because its usage is 65 * communicated to the client, and therefore more likely to be used also 66 * by the client. 67 * 68 * @param realm The HTTP Basic authentication realm 69 * @param charset The Charset to decode incoming credentials from the client 70 * @throws NullPointerException if realm or charset are {@code null} 71 * @throws IllegalArgumentException if realm is an empty string 72 */ BasicAuthenticator(String realm, Charset charset)73 public BasicAuthenticator (String realm, Charset charset) { 74 Objects.requireNonNull(charset); 75 if (realm.isEmpty()) // implicit NPE check 76 throw new IllegalArgumentException("realm must not be empty"); 77 this.realm = realm; 78 this.charset = charset; 79 this.isUTF8 = charset.equals(UTF_8); 80 } 81 82 /** 83 * returns the realm this BasicAuthenticator was created with 84 * @return the authenticator's realm string. 85 */ getRealm()86 public String getRealm () { 87 return realm; 88 } 89 authenticate(HttpExchange t)90 public Result authenticate (HttpExchange t) 91 { 92 Headers rmap = t.getRequestHeaders(); 93 /* 94 * look for auth token 95 */ 96 String auth = rmap.getFirst ("Authorization"); 97 if (auth == null) { 98 setAuthHeader(t); 99 return new Authenticator.Retry (401); 100 } 101 int sp = auth.indexOf (' '); 102 if (sp == -1 || !auth.substring(0, sp).equals ("Basic")) { 103 return new Authenticator.Failure (401); 104 } 105 byte[] b = Base64.getDecoder().decode(auth.substring(sp+1)); 106 String userpass = new String (b, charset); 107 int colon = userpass.indexOf (':'); 108 String uname = userpass.substring (0, colon); 109 String pass = userpass.substring (colon+1); 110 111 if (checkCredentials (uname, pass)) { 112 return new Authenticator.Success ( 113 new HttpPrincipal ( 114 uname, realm 115 ) 116 ); 117 } else { 118 /* reject the request again with 401 */ 119 setAuthHeader(t); 120 return new Authenticator.Failure(401); 121 } 122 } 123 setAuthHeader(HttpExchange t)124 private void setAuthHeader(HttpExchange t) { 125 Headers map = t.getResponseHeaders(); 126 var authString = "Basic realm=" + "\"" + realm + "\"" + 127 (isUTF8 ? ", charset=\"UTF-8\"" : ""); 128 map.set ("WWW-Authenticate", authString); 129 } 130 131 /** 132 * called for each incoming request to verify the 133 * given name and password in the context of this 134 * Authenticator's realm. Any caching of credentials 135 * must be done by the implementation of this method 136 * @param username the username from the request 137 * @param password the password from the request 138 * @return <code>true</code> if the credentials are valid, 139 * <code>false</code> otherwise. 140 */ checkCredentials(String username, String password)141 public abstract boolean checkCredentials (String username, String password); 142 } 143 144