mirror of https://github.com/alibaba/arthas.git
Add auth command, support authentication (#1724)
parent
985963b9d1
commit
e520b10319
@ -0,0 +1,69 @@
|
||||
package com.taobao.arthas.core.security;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* Basic {@link Principal}.
|
||||
*
|
||||
* @author hengyunabc 2021-03-04
|
||||
*/
|
||||
public final class BasicPrincipal implements Principal {
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
|
||||
public BasicPrincipal(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((password == null) ? 0 : password.hashCode());
|
||||
result = prime * result + ((username == null) ? 0 : username.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
BasicPrincipal other = (BasicPrincipal) obj;
|
||||
if (password == null) {
|
||||
if (other.password != null)
|
||||
return false;
|
||||
} else if (!password.equals(other.password))
|
||||
return false;
|
||||
if (username == null) {
|
||||
if (other.username != null)
|
||||
return false;
|
||||
} else if (!username.equals(other.username))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// do not display the password
|
||||
return "BasicPrincipal[" + username + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package com.taobao.arthas.core.security;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
/**
|
||||
* A {@link SecurityAuthenticator} allows to plugin custom authenticators, such
|
||||
* as JAAS based or custom implementations.
|
||||
*/
|
||||
public interface SecurityAuthenticator {
|
||||
|
||||
|
||||
boolean needLogin();
|
||||
|
||||
/**
|
||||
* Sets the name of the realm to use.
|
||||
*/
|
||||
void setName(String name);
|
||||
|
||||
/**
|
||||
* Gets the name of the realm.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Sets the role class names (separated by comma)
|
||||
* <p/>
|
||||
* By default if no explicit role class names has been configured, then this
|
||||
* implementation will assume the {@link Subject}
|
||||
* {@link java.security.Principal}s is a role if the classname contains the word
|
||||
* <tt>role</tt> (lower cased).
|
||||
*
|
||||
* @param names a list of FQN class names for role
|
||||
* {@link java.security.Principal} implementations.
|
||||
*/
|
||||
void setRoleClassNames(String names);
|
||||
|
||||
/**
|
||||
* Attempts to login the {@link java.security.Principal} on this realm.
|
||||
* <p/>
|
||||
* The login is a success if no Exception is thrown, and a {@link Subject} is
|
||||
* returned.
|
||||
*
|
||||
* @param principal the principal
|
||||
* @return the subject for the logged in principal, must <b>not</b> be
|
||||
* <tt>null</tt>
|
||||
* @throws LoginException is thrown if error logging in the
|
||||
* {@link java.security.Principal}
|
||||
*/
|
||||
Subject login(Principal principal) throws LoginException;
|
||||
|
||||
/**
|
||||
* Attempt to logout the subject.
|
||||
*
|
||||
* @param subject subject to logout
|
||||
* @throws LoginException is thrown if error logging out subject
|
||||
*/
|
||||
void logout(Subject subject) throws LoginException;
|
||||
|
||||
/**
|
||||
* Gets the user roles from the given {@link Subject}
|
||||
*
|
||||
* @param subject the subject
|
||||
* @return <tt>null</tt> if no roles, otherwise a String with roles separated by
|
||||
* comma.
|
||||
*/
|
||||
String getUserRoles(Subject subject);
|
||||
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.security.BasicPrincipal;
|
||||
import com.taobao.arthas.core.security.SecurityAuthenticator;
|
||||
import com.taobao.arthas.core.server.ArthasBootstrap;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSession;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.handler.codec.http.DefaultHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.util.Attribute;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2021-03-03
|
||||
*
|
||||
*/
|
||||
public final class BasicHttpAuthenticatorHandler extends ChannelDuplexHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BasicHttpAuthenticatorHandler.class);
|
||||
|
||||
private HttpSessionManager httpSessionManager;
|
||||
|
||||
private SecurityAuthenticator securityAuthenticator = ArthasBootstrap.getInstance().getSecurityAuthenticator();
|
||||
|
||||
public BasicHttpAuthenticatorHandler(HttpSessionManager httpSessionManager) {
|
||||
this.httpSessionManager = httpSessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (!securityAuthenticator.needLogin()) {
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean authed = false;
|
||||
if (msg instanceof HttpRequest) {
|
||||
HttpRequest httpRequest = (HttpRequest) msg;
|
||||
|
||||
// 判断session里是否有已登陆信息
|
||||
HttpSession session = httpSessionManager.getOrCreateHttpSession(ctx, httpRequest);
|
||||
if (session != null && session.getAttribute(ArthasConstants.SUBJECT_KEY) != null) {
|
||||
authed = true;
|
||||
}
|
||||
|
||||
// 判断请求header里是否带有 username/password
|
||||
if (!authed) {
|
||||
BasicPrincipal principal = extractBasicAuthSubject(httpRequest);
|
||||
Subject subject = securityAuthenticator.login(principal);
|
||||
if (subject != null) {
|
||||
authed = true;
|
||||
session.setAttribute(ArthasConstants.SUBJECT_KEY, subject);
|
||||
}
|
||||
}
|
||||
|
||||
if (!authed) {
|
||||
// restricted resource, so send back 401 to require valid username/password
|
||||
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED);
|
||||
response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"arthas webconsole\"");
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
|
||||
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
|
||||
ctx.writeAndFlush(response);
|
||||
// close the channel
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
if (msg instanceof HttpResponse) {
|
||||
// write cookie
|
||||
HttpResponse response = (HttpResponse) msg;
|
||||
Attribute<HttpSession> attribute = ctx.channel().attr(HttpSessionManager.SESSION_KEY);
|
||||
HttpSession session = attribute.get();
|
||||
if (session != null) {
|
||||
HttpSessionManager.setSessionCookie(response, session);
|
||||
}
|
||||
}
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the username and password details from the HTTP basic header
|
||||
* Authorization.
|
||||
* <p/>
|
||||
* This requires that the <tt>Authorization</tt> HTTP header is provided, and
|
||||
* its using Basic. Currently Digest is <b>not</b> supported.
|
||||
*
|
||||
* @return {@link HttpPrincipal} with username and password details, or
|
||||
* <tt>null</tt> if not possible to extract
|
||||
*/
|
||||
protected static BasicPrincipal extractBasicAuthSubject(HttpRequest request) {
|
||||
String auth = request.headers().get(HttpHeaderNames.AUTHORIZATION);
|
||||
if (auth != null) {
|
||||
String constraint = StringUtils.before(auth, " ");
|
||||
if (constraint != null) {
|
||||
if ("Basic".equalsIgnoreCase(constraint.trim())) {
|
||||
String decoded = StringUtils.after(auth, " ");
|
||||
// the decoded part is base64 encoded, so we need to decode that
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(decoded.getBytes());
|
||||
ByteBuf out = Base64.decode(buf);
|
||||
String userAndPw = out.toString(Charset.defaultCharset());
|
||||
String username = StringUtils.before(userAndPw, ":");
|
||||
String password = StringUtils.after(userAndPw, ":");
|
||||
BasicPrincipal principal = new BasicPrincipal(username, password);
|
||||
logger.debug("Extracted Basic Auth principal from HTTP header: {}", principal);
|
||||
return principal;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSession;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.termd.core.http.HttpTtyConnection;
|
||||
|
||||
/**
|
||||
* 从http请求传递过来的 session 信息。解析websocket创建的 term 还需要登陆验证问题
|
||||
*
|
||||
* @author hengyunabc 2021-03-04
|
||||
*
|
||||
*/
|
||||
public class ExtHttpTtyConnection extends HttpTtyConnection {
|
||||
private ChannelHandlerContext context;
|
||||
|
||||
public ExtHttpTtyConnection(ChannelHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(byte[] buffer) {
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
byteBuf.writeBytes(buffer);
|
||||
if (context != null) {
|
||||
context.writeAndFlush(new TextWebSocketFrame(byteBuf));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedule(Runnable task, long delay, TimeUnit unit) {
|
||||
if (context != null) {
|
||||
context.executor().schedule(task, delay, unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
if (context != null) {
|
||||
context.executor().execute(task);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> extSessions() {
|
||||
if (context != null) {
|
||||
HttpSession httpSession = HttpSessionManager.getHttpSessionFromContext(context);
|
||||
if (httpSession != null) {
|
||||
Object subject = httpSession.getAttribute(ArthasConstants.SUBJECT_KEY);
|
||||
if (subject != null) {
|
||||
Map<String, Object> result = new HashMap<String, Object>();
|
||||
result.put(ArthasConstants.SUBJECT_KEY, subject);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
|
||||
package com.taobao.arthas.core.shell.term.impl.http.session;
|
||||
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2021-03-03
|
||||
*
|
||||
*/
|
||||
public interface HttpSession {
|
||||
|
||||
/**
|
||||
* Returns the time when this session was created, measured in milliseconds
|
||||
* since midnight January 1, 1970 GMT.
|
||||
*
|
||||
* @return a <code>long</code> specifying when this session was created,
|
||||
* expressed in milliseconds since 1/1/1970 GMT
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public long getCreationTime();
|
||||
|
||||
/**
|
||||
* Returns a string containing the unique identifier assigned to this session.
|
||||
* The identifier is assigned by the servlet container and is implementation
|
||||
* dependent.
|
||||
*
|
||||
* @return a string specifying the identifier assigned to this session
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public String getId();
|
||||
|
||||
/**
|
||||
* Returns the last time the client sent a request associated with this session,
|
||||
* as the number of milliseconds since midnight January 1, 1970 GMT, and marked
|
||||
* by the time the container received the request.
|
||||
* <p>
|
||||
* Actions that your application takes, such as getting or setting a value
|
||||
* associated with the session, do not affect the access time.
|
||||
*
|
||||
* @return a <code>long</code> representing the last time the client sent a
|
||||
* request associated with this session, expressed in milliseconds since
|
||||
* 1/1/1970 GMT
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public long getLastAccessedTime();
|
||||
|
||||
/**
|
||||
* Specifies the time, in seconds, between client requests before the servlet
|
||||
* container will invalidate this session. A zero or negative time indicates
|
||||
* that the session should never timeout.
|
||||
*
|
||||
* @param interval An integer specifying the number of seconds
|
||||
*/
|
||||
public void setMaxInactiveInterval(int interval);
|
||||
|
||||
/**
|
||||
* Returns the maximum time interval, in seconds, that the servlet container
|
||||
* will keep this session open between client accesses. After this interval, the
|
||||
* servlet container will invalidate the session. The maximum time interval can
|
||||
* be set with the <code>setMaxInactiveInterval</code> method. A zero or
|
||||
* negative time indicates that the session should never timeout.
|
||||
*
|
||||
* @return an integer specifying the number of seconds this session remains open
|
||||
* between client requests
|
||||
* @see #setMaxInactiveInterval
|
||||
*/
|
||||
public int getMaxInactiveInterval();
|
||||
|
||||
/**
|
||||
* Returns the object bound with the specified name in this session, or
|
||||
* <code>null</code> if no object is bound under the name.
|
||||
*
|
||||
* @param name a string specifying the name of the object
|
||||
* @return the object with the specified name
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public Object getAttribute(String name);
|
||||
|
||||
/**
|
||||
* Returns an <code>Enumeration</code> of <code>String</code> objects containing
|
||||
* the names of all the objects bound to this session.
|
||||
*
|
||||
* @return an <code>Enumeration</code> of <code>String</code> objects specifying
|
||||
* the names of all the objects bound to this session
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public Enumeration<String> getAttributeNames();
|
||||
|
||||
/**
|
||||
* Binds an object to this session, using the name specified. If an object of
|
||||
* the same name is already bound to the session, the object is replaced.
|
||||
* <p>
|
||||
* After this method executes, and if the new object implements
|
||||
* <code>HttpSessionBindingListener</code>, the container calls
|
||||
* <code>HttpSessionBindingListener.valueBound</code>. The container then
|
||||
* notifies any <code>HttpSessionAttributeListener</code>s in the web
|
||||
* application.
|
||||
* <p>
|
||||
* If an object was already bound to this session of this name that implements
|
||||
* <code>HttpSessionBindingListener</code>, its
|
||||
* <code>HttpSessionBindingListener.valueUnbound</code> method is called.
|
||||
* <p>
|
||||
* If the value passed in is null, this has the same effect as calling
|
||||
* <code>removeAttribute()</code>.
|
||||
*
|
||||
* @param name the name to which the object is bound; cannot be null
|
||||
* @param value the object to be bound
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public void setAttribute(String name, Object value);
|
||||
|
||||
/**
|
||||
* Removes the object bound with the specified name from this session. If the
|
||||
* session does not have an object bound with the specified name, this method
|
||||
* does nothing.
|
||||
* <p>
|
||||
* After this method executes, and if the object implements
|
||||
* <code>HttpSessionBindingListener</code>, the container calls
|
||||
* <code>HttpSessionBindingListener.valueUnbound</code>. The container then
|
||||
* notifies any <code>HttpSessionAttributeListener</code>s in the web
|
||||
* application.
|
||||
*
|
||||
* @param name the name of the object to remove from this session
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public void removeAttribute(String name);
|
||||
|
||||
/**
|
||||
* Invalidates this session then unbinds any objects bound to it.
|
||||
*
|
||||
* @exception IllegalStateException if this method is called on an already
|
||||
* invalidated session
|
||||
*/
|
||||
public void invalidate();
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the client does not yet know about the session
|
||||
* or if the client chooses not to join the session. For example, if the server
|
||||
* used only cookie-based sessions, and the client had disabled the use of
|
||||
* cookies, then a session would be new on each request.
|
||||
*
|
||||
* @return <code>true</code> if the server has created a session, but the client
|
||||
* has not yet joined
|
||||
* @exception IllegalStateException if this method is called on an already
|
||||
* invalidated session
|
||||
*/
|
||||
public boolean isNew();
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http.session;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* An LRU cache, based on <code>LinkedHashMap</code>.
|
||||
*
|
||||
* <p>
|
||||
* This cache has a fixed maximum number of elements (<code>cacheSize</code>).
|
||||
* If the cache is full and another entry is added, the LRU (least recently
|
||||
* used) entry is dropped.
|
||||
*
|
||||
* <p>
|
||||
* This class is thread-safe. All methods of this class are synchronized.
|
||||
*
|
||||
* <p>
|
||||
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
|
||||
* Multi-licensed: EPL / LGPL / GPL / AL / BSD.
|
||||
*/
|
||||
public class LRUCache<K, V> {
|
||||
|
||||
private static final float hashTableLoadFactor = 0.75f;
|
||||
|
||||
private LinkedHashMap<K, V> map;
|
||||
private int cacheSize;
|
||||
|
||||
/**
|
||||
* Creates a new LRU cache.
|
||||
*
|
||||
* @param cacheSize the maximum number of entries that will be kept in this
|
||||
* cache.
|
||||
*/
|
||||
public LRUCache(int cacheSize) {
|
||||
this.cacheSize = cacheSize;
|
||||
int hashTableCapacity = (int) Math.ceil(cacheSize / hashTableLoadFactor) + 1;
|
||||
map = new LinkedHashMap<K, V>(hashTableCapacity, hashTableLoadFactor, true) {
|
||||
// (an anonymous inner class)
|
||||
private static final long serialVersionUID = 1;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||
return size() > LRUCache.this.cacheSize;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an entry from the cache.<br>
|
||||
* The retrieved entry becomes the MRU (most recently used) entry.
|
||||
*
|
||||
* @param key the key whose associated value is to be returned.
|
||||
* @return the value associated to this key, or null if no value with this key
|
||||
* exists in the cache.
|
||||
*/
|
||||
public synchronized V get(K key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entry to this cache. The new entry becomes the MRU (most recently
|
||||
* used) entry. If an entry with the specified key already exists in the cache,
|
||||
* it is replaced by the new entry. If the cache is full, the LRU (least
|
||||
* recently used) entry is removed from the cache.
|
||||
*
|
||||
* @param key the key with which the specified value is to be associated.
|
||||
* @param value a value to be associated with the specified key.
|
||||
*/
|
||||
public synchronized void put(K key, V value) {
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache.
|
||||
*/
|
||||
public synchronized void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of used entries in the cache.
|
||||
*
|
||||
* @return the number of entries currently in the cache.
|
||||
*/
|
||||
public synchronized int usedEntries() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a <code>Collection</code> that contains a copy of all cache entries.
|
||||
*
|
||||
* @return a <code>Collection</code> with a copy of the cache content.
|
||||
*/
|
||||
public synchronized Collection<Map.Entry<K, V>> getAll() {
|
||||
return new ArrayList<Map.Entry<K, V>>(map.entrySet());
|
||||
}
|
||||
|
||||
} // end class LRUCache
|
@ -0,0 +1,79 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http.session;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2021-03-03
|
||||
*
|
||||
*/
|
||||
public class SimpleHttpSession implements HttpSession {
|
||||
private Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
|
||||
|
||||
private String id;
|
||||
|
||||
public SimpleHttpSession() {
|
||||
id = StringUtils.randomString(32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastAccessedTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(int interval) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxInactiveInterval() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
return attributes.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames() {
|
||||
return Collections.enumeration(this.attributes.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object value) {
|
||||
attributes.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
attributes.remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.taobao.arthas.core.security;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2021-03-04
|
||||
*
|
||||
*/
|
||||
public class SecurityAuthenticatorImplTest {
|
||||
|
||||
@Test
|
||||
public void test1() throws LoginException {
|
||||
String username = "test";
|
||||
String password = "ppp";
|
||||
SecurityAuthenticatorImpl auth = new SecurityAuthenticatorImpl(username, password);
|
||||
|
||||
Assertions.assertThat(auth.needLogin()).isTrue();
|
||||
|
||||
Principal principal = new BasicPrincipal(username, password);
|
||||
Subject subject = auth.login(principal);
|
||||
|
||||
Assertions.assertThat(subject).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
String username = "test";
|
||||
String password = null;
|
||||
SecurityAuthenticatorImpl auth = new SecurityAuthenticatorImpl(username, password);
|
||||
Assertions.assertThat(auth.needLogin()).isTrue();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
auth
|
||||
===
|
||||
|
||||
> Authenticates the current session
|
||||
|
||||
### Configure username and password
|
||||
|
||||
When attaching, you can specify a password on the command line. such as:
|
||||
|
||||
```
|
||||
java -jar arthas-boot.jar --password ppp
|
||||
```
|
||||
|
||||
* The user can be specified by the `--username` option, the default value is `arthas`.
|
||||
* You can also configure username/password in arthas.properties. The priority of the command line is higher than that of the configuration file.
|
||||
|
||||
### Authenticate in the telnet console
|
||||
|
||||
After connecting to arthas, directly executing the command will prompt for authentication:
|
||||
|
||||
```bash
|
||||
[arthas@37430]$ help
|
||||
Error! command not permitted, try to use 'auth' command to authenticates.
|
||||
```
|
||||
|
||||
Use the `auth` command to authenticate, and you can execute other commands after success.
|
||||
|
||||
```
|
||||
[arthas@37430]$ auth ppp
|
||||
Authentication result: true
|
||||
```
|
||||
|
||||
* The user can be specified by the `--username` option, the default value is `arthas`.
|
||||
|
||||
### Web console Authentication
|
||||
|
||||
Open the browser, there will be a pop-up window prompting you to enter your username and password.
|
||||
|
||||
After success, you can directly connect to the web console.
|
||||
|
||||
### HTTP API Authentication
|
||||
|
||||
Arthas uses the HTTP standard Basic Authorization.
|
||||
|
||||
* Reference: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication)
|
Loading…
Reference in New Issue