Skip to content

Instantly share code, notes, and snippets.

@seraphy
Last active September 15, 2015 01:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seraphy/f85581b2ec7b50035caa to your computer and use it in GitHub Desktop.
Save seraphy/f85581b2ec7b50035caa to your computer and use it in GitHub Desktop.
JAASによる対話的なログインと、Principalの設定をもつPolicyのセキュリティマネージャを有効にした場合のユーザーによる権限の切り替えの実装例.
/** Login Configuration for the JAAS Sample Application **/
Sample {
jp.seraphyware.jaasexample.SampleLoginModule required debug=true;
//com.sun.security.auth.module.NTLoginModule optional debug=true;
//com.sun.security.auth.module.Krb5LoginModule required debug=true;
};
package jp.seraphyware.jaasexample;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.CodeSource;
import java.security.GeneralSecurityException;
import java.security.Policy;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
/**
* JAASを使いログイン、ログアウトと、ログイン状態での権限の行使などのサンプル.<br>
*/
public class Main extends JFrame {
/**
* ロガー
*/
private static final Logger logger = Logger.getLogger(Main.class.getName());
/**
* ログイン情報を表示したりユーザーと対話的に入力するためのコールバック
*/
private class MyCallbackHandler implements CallbackHandler {
/**
* Invoke an array of Callbacks.
*
* <p>
*
* @param callbacks
* an array of <code>Callback</code> objects which contain
* the information requested by an underlying security
* service to be retrieved or displayed.
*
* @exception java.io.IOException
* if an input or output error occurs.
* <p>
*
* @exception UnsupportedCallbackException
* if the implementation of this method does not support
* one or more of the Callbacks specified in the
* <code>callbacks</code> parameter.
*/
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
NameCallback nc = null;
PasswordCallback pc = null;
for (Callback callback : callbacks) {
if (callback instanceof TextOutputCallback) {
int dlgType = 0;
String title = "";
TextOutputCallback toc = (TextOutputCallback) callback;
switch (toc.getMessageType()) {
case TextOutputCallback.INFORMATION:
dlgType = JOptionPane.INFORMATION_MESSAGE;
title = "INFORMATION";
break;
case TextOutputCallback.ERROR:
dlgType = JOptionPane.ERROR_MESSAGE;
title = "ERROR";
break;
case TextOutputCallback.WARNING:
dlgType = JOptionPane.WARNING_MESSAGE;
title = "WARNING";
break;
default:
throw new IOException("Unsupported message type: "
+ toc.getMessageType());
}
JOptionPane.showMessageDialog(Main.this, toc.getMessage(),
title, dlgType);
} else if (callback instanceof NameCallback) {
nc = (NameCallback) callback;
} else if (callback instanceof PasswordCallback) {
pc = (PasswordCallback) callback;
} else {
throw new UnsupportedCallbackException(callback,
"Unrecognized Callback");
}
}
if (nc != null || pc != null) {
JPanel pnl = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
int y = 0;
JTextField txtUserName = null;
if (nc != null) {
gbc.gridx = 0;
gbc.gridy = y;
gbc.ipadx = 5;
gbc.weightx = 0;
pnl.add(new JLabel("User Name"), gbc);
txtUserName = new JTextField();
Dimension dimUserName = txtUserName.getPreferredSize();
dimUserName.width = 150;
txtUserName.setPreferredSize(dimUserName);
if (nc != null) {
txtUserName.setText(nc.getDefaultName());
}
gbc.gridx = 1;
gbc.gridy = y;
gbc.weightx = 1.;
pnl.add(txtUserName, gbc);
y++;
}
JPasswordField txtPassword = null;
if (pc != null) {
gbc.gridx = 0;
gbc.gridy = y;
gbc.weightx = 0;
pnl.add(new JLabel("Password"), gbc);
txtPassword = new JPasswordField();
Dimension dimPassword = txtPassword.getPreferredSize();
dimPassword.width = 150;
txtPassword.setPreferredSize(dimPassword);
gbc.gridx = 1;
gbc.gridy = y;
gbc.weightx = 1.;
pnl.add(txtPassword, gbc);
y++;
}
int ret = JOptionPane.showConfirmDialog(Main.this, pnl,
"Login", JOptionPane.OK_CANCEL_OPTION);
if (ret == JOptionPane.OK_OPTION) {
if (nc != null) {
nc.setName(txtUserName.getText());
}
if (pc != null) {
pc.setPassword(txtPassword.getPassword());
}
}
}
}
}
/**
* ログインコンテキスト
*/
private LoginContext lc = null;
/**
* サブジェクト
*/
private Subject subject = new Subject();
public Main() {
try {
setTitle("JAAS Example");
setSize(300, 300);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
onClose();
}
@Override
public void windowActivated(WindowEvent e) {
onActivate();
}
});
setLayout(new BorderLayout());
AbstractAction actLogin = new AbstractAction("Login") {
@Override
public void actionPerformed(ActionEvent e) {
onLogin();
}
};
AbstractAction actShowLoginInfo = new AbstractAction("Info") {
@Override
public void actionPerformed(ActionEvent e) {
onShowLoginInfo();
}
};
AbstractAction actDoAs = new AbstractAction("doAs(read)") {
@Override
public void actionPerformed(ActionEvent e) {
onDoAs();
}
};
AbstractAction actDoAs2 = new AbstractAction("doAs(write)") {
@Override
public void actionPerformed(ActionEvent e) {
onDoAs2();
}
};
AbstractAction actLogout = new AbstractAction("Logout") {
@Override
public void actionPerformed(ActionEvent e) {
onLogout();
}
};
JButton btnLogin = new JButton(actLogin);
JButton btnShowLoginInfo = new JButton(actShowLoginInfo);
JButton btnDoAs = new JButton(actDoAs);
JButton btnDoAs2 = new JButton(actDoAs2);
JButton btnLogout = new JButton(actLogout);
Box box = Box.createHorizontalBox();
box.add(btnLogin);
box.add(btnShowLoginInfo);
box.add(btnDoAs);
box.add(btnDoAs2);
box.add(btnLogout);
JTextArea txtArea = new JTextArea();
txtArea.setText(
"ユーザー名が「user」で始まるものを\r\n" +
"読み込み可能な有効なユーザーとする.\r\n\r\n" +
"「user123」は書き込み権限をもつ。\r\n\r\n" +
"パスワードはユーザー名と同じ.\r\n\r\n" +
"テスト用ファイルは\r\n" +
EXAMPLE_FILE_NAME + "に作成される。");
Container contentPane = getContentPane();
contentPane.add(box, BorderLayout.NORTH);
contentPane.add(new JScrollPane(txtArea), BorderLayout.CENTER);
} catch (RuntimeException ex) {
dispose();
throw ex;
}
}
protected void onClose() {
dispose();
}
protected void onLogin() {
try {
lc.login();
JOptionPane.showMessageDialog(this, "logged on!");
} catch (LoginException le) {
String msg = "Authentication failed: " + le;
logger.log(Level.WARNING, msg, le);
JOptionPane.showMessageDialog(this, msg, "ERROR",
JOptionPane.WARNING_MESSAGE);
}
}
protected void onLogout() {
try {
// attempt authentication
lc.logout();
JOptionPane.showMessageDialog(this, "logged out!");
} catch (LoginException le) {
String msg = "Logout failed: " + le;
logger.log(Level.WARNING, msg, le);
JOptionPane.showMessageDialog(this, msg, "ERROR",
JOptionPane.WARNING_MESSAGE);
}
}
protected void onShowLoginInfo() {
StringBuilder buf = new StringBuilder();
for (Principal p : subject.getPrincipals()) {
String name = p.getName();
buf.append(p.getClass()).append("\r\n").append(p.toString())
.append("\r\n").append("name=").append(name)
.append("\r\n");
}
JOptionPane.showMessageDialog(this, buf.toString());
}
private static final String EXAMPLE_FILE_NAME = "C:\\temp\\jaasexample.txt";
protected void onDoAs() {
try {
String ret = Subject.doAsPrivileged(subject,
new PrivilegedAction<String>() {
@Override
public String run() {
try{
return String.join("\r\n", Files.readAllLines(Paths
.get(EXAMPLE_FILE_NAME)));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}, null);
JOptionPane.showMessageDialog(this, ret);
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(this, ex.toString(), "ERROR",
JOptionPane.ERROR_MESSAGE);
}
}
protected void onDoAs2() {
try {
String ret = Subject.doAsPrivileged(subject,
new PrivilegedAction<String>() {
@Override
public String run() {
try{
Path path = Paths.get(EXAMPLE_FILE_NAME);
ArrayList<String> lines = new ArrayList<>();
String msg = new Timestamp(System.currentTimeMillis()).toString();
lines.add(msg);
Files.write(path, lines);
return msg;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}, null);
JOptionPane.showMessageDialog(this, ret);
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(this, ex.toString(), "ERROR",
JOptionPane.ERROR_MESSAGE);
}
}
protected void onActivate() {
// ユーザの認証には、まず javax.security.auth.login.LoginContextが必要です。
// JAASログイン構成ファイル内で「サンプル」という名前のエントリで
// 指定されたLoginModuleの実装を使用すると、
// 指定したCallbackHandlerを使用するように指示します。
if (lc == null) {
try {
lc = new LoginContext("Sample", subject, new MyCallbackHandler());
} catch (LoginException | SecurityException le) {
String msg = "Cannot create LoginContext. " + le;
logger.log(Level.SEVERE, msg, le);
JOptionPane.showMessageDialog(this, msg, "ERROR",
JOptionPane.ERROR_MESSAGE);
dispose();
}
}
}
/**
* エントリポイント
*/
public static void main(String[] args) throws GeneralSecurityException {
// jaas.configファイルのURLを設定する.
URL configURL = Main.class.getResource("jaas.config");
System.setProperty("java.security.auth.login.config",
"=" + configURL.toExternalForm()); // 先頭に=をつけると既定値は無視される。
// もしくは
// java.security.Security.setProperty("login.config.url.1", "xxx");
// セキュリティマネージャとポリシーファイルの有効化
enableSecurityManagerByPolicyFile();
try {
UIManager.setLookAndFeel(UIManager
.getCrossPlatformLookAndFeelClassName());
} catch (Exception ex) {
logger.log(Level.INFO, ex.toString(), ex);
}
SwingUtilities.invokeLater(() -> {
Main main = new Main();
main.setLocationByPlatform(true);
main.setVisible(true);
});
}
/**
* アプリケーション用のポリシーファイルに従いセキュリティポリシーを構成し、
* セキュリティマネージャを有効とする.<br>
*/
public static void enableSecurityManagerByPolicyFile() {
// リソースからポリシーファイルの取得
URL policyURL = Main.class.getResource("security.policy");
if (policyURL == null) {
throw new SecurityException("ポリシーファイルがみつかりません");
}
// このクラスのあるプロテクションドメインのコードソースを取得する.
// ポリシーファイル中の"app.codebase"変数で展開するもの.
ProtectionDomain pd = Main.class.getProtectionDomain();
CodeSource cs = pd.getCodeSource();
URL loc = cs.getLocation();
// ポリシーファイルのURLをシステムプロパティに設定する
// 先頭にイコールがある場合はデフォルトのセキュリティポリシーを無視する.
// http://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html
System.setProperty("java.security.policy", "=" + policyURL.toExternalForm());
// システムプロパティにセットする
System.setProperty("app.codebase", loc.toExternalForm());
// セキュリティマネージャを有効にする.
// 明示的にポリシーファイルが指定されているので、
// アプリ用のポリシーファイルが読み込まれる.
System.setSecurityManager(new SecurityManager());
Policy policy = Policy.getPolicy();
System.out.println("policy=" + policy);
// policy=sun.security.provider.PolicyFile@28d93b30
}
}
package jp.seraphyware.jaasexample;
import java.util.Arrays;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
public class SampleLoginModule implements LoginModule {
private static final Logger logger = Logger
.getLogger(SampleLoginModule.class.getName());
private Subject subject;
private SamplePrincipal userPrincipal;
private SamplePrincipal userPrincipal2;
private CallbackHandler callbackHandler;
private Map<String, ?> sharedState;
private Map<String, ?> options;
// configurable option
private boolean debug = false;
// the authentication status
private boolean succeeded = false;
// username and password
private String username;
/**
* Initialize this <code>LoginModule</code>.
*
* <p>
*
* @param subject
* 認証を受ける Subject
*
* @param callbackHandler
* エンドユーザーとの通信 (ユーザー名とパスワードの入力など) に使用される CallbackHandler
*
* @param sharedState
* 構成されたほかの LoginModule と共有する状態
*
* @param options
* この特定のLoginModule用ログインConfigurationで指定されたオプション。
*/
@Override
public void initialize(
Subject subject,
CallbackHandler callbackHandler,
Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
debug = Boolean.parseBoolean((String) options.get("debug"));
logger.log(Level.FINE, "sharedState=" + this.sharedState);
logger.log(Level.FINE, "options=" + this.options);
}
/**
* Authenticate the user by prompting for a user name and password.
*
* <p>
*
* @return true in all cases since this <code>LoginModule</code> should not
* be ignored.
*
* @exception FailedLoginException
* if the authentication fails.
* <p>
*
* @exception LoginException
* if this <code>LoginModule</code> is unable to perform the
* authentication.
*/
@Override
public boolean login() throws LoginException {
// prompt for a user name and password
if (callbackHandler == null) {
throw new LoginException("Error: no CallbackHandler available "
+ "to garner authentication information from the user");
}
NameCallback nc = new NameCallback("user name: ");
PasswordCallback pc = new PasswordCallback("password: ", false);
Callback[] callbacks = {nc, pc};
char password[];
try {
callbackHandler.handle(callbacks);
username = nc.getName();
char[] tmpPassword = pc.getPassword();
if (username == null) {
username = "";
}
if (tmpPassword == null) {
tmpPassword = new char[0];
}
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
pc.clearPassword();
} catch (java.io.IOException ioe) {
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException uce) {
throw new LoginException("Error: " + uce.getCallback().toString()
+ " not available to garner authentication information "
+ "from the user");
}
// print debugging information
if (debug) {
logger.log(Level.INFO,
"[SampleLoginModule] user entered user name: " + username);
}
// パスワードの確認
char[] verifyPassword = username.toCharArray();
succeeded = (username.startsWith("user") && Arrays.equals(
verifyPassword, password));
// パスワードのメモリ上からのクリア.
Arrays.fill(verifyPassword, ' ');
Arrays.fill(password, ' ');
// 失敗させる場合は例外、無視する場合はfalseを返す.
if (!succeeded) {
throw new FailedLoginException("User Name Incorrect");
}
return succeeded;
}
/**
* <p>
* This method is called if the LoginContext's overall authentication
* succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules succeeded).
*
* <p>
* If this LoginModule's own authentication attempt succeeded (checked by
* retrieving the private state saved by the <code>login</code> method),
* then this method associates a <code>SamplePrincipal</code> with the
* <code>Subject</code> located in the <code>LoginModule</code>. If this
* LoginModule's own authentication attempted failed, then this method
* removes any state that was originally saved.
*
* <p>
*
* @exception LoginException
* if the commit fails.
*
* @return true if this LoginModule's own login and commit attempts
* succeeded, or false otherwise.
*/
@Override
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
}
userPrincipal2 = null;
if (username.equals("user123")) {
userPrincipal2 = new SamplePrincipal("powerusers");
if (!subject.getPrincipals().contains(userPrincipal2)) {
subject.getPrincipals().add(userPrincipal2);
}
}
userPrincipal = new SamplePrincipal("users");
if (!subject.getPrincipals().contains(userPrincipal)) {
subject.getPrincipals().add(userPrincipal);
}
if (debug) {
logger.log(Level.INFO,
"[SampleLoginModule] added SamplePrincipal to Subject");
}
username = null;
return true;
}
/**
* <p>
* This method is called if the LoginContext's overall authentication
* failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules did not succeed).
*
* <p>
* If this LoginModule's own authentication attempt succeeded (checked by
* retrieving the private state saved by the <code>login</code> and
* <code>commit</code> methods), then this method cleans up any state that
* was originally saved.
*
* <p>
*
* @exception LoginException
* if the abort fails.
*
* @return false if this LoginModule's own login and/or commit attempts
* failed, and true otherwise.
*/
@Override
public boolean abort() throws LoginException {
if (succeeded == false) {
return false;
}
logout();
return true;
}
/**
* Logout the user.
*
* <p>
* This method removes the <code>SamplePrincipal</code> that was added by
* the <code>commit</code> method.
*
* <p>
*
* @exception LoginException
* if the logout fails.
*
* @return true in all cases since this <code>LoginModule</code> should not
* be ignored.
*/
@Override
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
if (userPrincipal2 != null) {
subject.getPrincipals().remove(userPrincipal2);
}
succeeded = false;
username = null;
userPrincipal = null;
userPrincipal2 = null;
return true;
}
}
package jp.seraphyware.jaasexample;
import java.security.Principal;
public class SamplePrincipal implements Principal, java.io.Serializable {
/**
* @serial
*/
private String name;
/**
* Create a SamplePrincipal with a Sample username.
*
* <p>
*
* @param name
* the Sample username for this user.
*
* @exception NullPointerException
* if the <code>name</code> is <code>null</code>.
*/
public SamplePrincipal(String name) {
if (name == null) {
throw new NullPointerException("illegal null input");
}
System.out.println("**Create SamplePrincipal: " + name);
this.name = name;
}
/**
* Return the Sample username for this <code>SamplePrincipal</code>.
*
* <p>
*
* @return the Sample username for this <code>SamplePrincipal</code>
*/
public String getName() {
return name;
}
/**
* Return a string representation of this <code>SamplePrincipal</code>.
*
* <p>
*
* @return a string representation of this <code>SamplePrincipal</code>.
*/
public String toString() {
return ("SamplePrincipal: " + name);
}
/**
* Compares the specified Object with this <code>SamplePrincipal</code> for
* equality. Returns true if the given object is also a
* <code>SamplePrincipal</code> and the two SamplePrincipals have the same
* username.
*
* <p>
*
* @param o
* Object to be compared for equality with this
* <code>SamplePrincipal</code>.
*
* @return true if the specified Object is equal equal to this
* <code>SamplePrincipal</code>.
*/
public boolean equals(Object o) {
if (o == null)
return false;
if (this == o)
return true;
if (!(o instanceof SamplePrincipal))
return false;
SamplePrincipal that = (SamplePrincipal) o;
if (this.getName().equals(that.getName()))
return true;
return false;
}
/**
* Return a hash code for this <code>SamplePrincipal</code>.
*
* <p>
*
* @return a hash code for this <code>SamplePrincipal</code>.
*/
public int hashCode() {
return name.hashCode();
}
}
// jre/lib/ext上のライブラリ群
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// アプリケーション自身
grant codeBase "${app.codebase}" {
permission javax.security.auth.AuthPermission "createLoginContext.Sample";
permission javax.security.auth.AuthPermission "doAsPrivileged";
permission javax.security.auth.AuthPermission "modifyPrincipals";
permission java.security.SecurityPermission "getPolicy";
permission java.security.SecurityPermission "createAccessControlContext";
};
// usersの場合に許可されるパーミッション
grant codebase "${app.codebase}",
principal jp.seraphyware.jaasexample.SamplePrincipal "users" {
permission java.io.FilePermission "C:\\temp\\*", "read";
};
// power-usersの場合に許可されるパーミッション
grant codebase "${app.codebase}",
principal jp.seraphyware.jaasexample.SamplePrincipal "powerusers" {
permission java.io.FilePermission "C:\\temp\\*", "read, write, delete";
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment