基于JAAS实现角色权限控制:如何通过Principal类完成授权?
JAAS 授权控制实现方案(基于Principal)
核心思路
通过给JAAS的Subject添加角色类型的Principal,在执行操作前验证当前Subject是否包含对应权限的角色Principal,实现细粒度的授权控制。具体需要:
- 区分用户身份Principal与角色身份Principal
- 登录阶段根据用户分配对应角色并添加到Subject
- 操作执行前校验角色权限
代码修改步骤
1. 创建角色Principal类
新增RolePrincipal类,专门用于标识用户的角色权限:
import java.io.Serializable; import java.security.Principal; public class RolePrincipal implements Principal, Serializable { private static final long serialVersionUID = 1L; private final String roleName; public RolePrincipal(String roleName) { this.roleName = roleName; } @Override public String getName() { return roleName; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; RolePrincipal that = (RolePrincipal) obj; return roleName.equals(that.roleName); } @Override public int hashCode() { return roleName.hashCode(); } }
2. 完善用户Principal类
修正原LorduPrinciple的序列化ID拼写错误,完善hashCode方法(符合Principal规范):
import java.io.Serializable; import java.security.Principal; public class LorduPrinciple implements Principal, Serializable { private static final long serialVersionUID = 1L; // 修正拼写错误 private final String username; public LorduPrinciple(String username) { this.username = username; } @Override public String getName() { return username; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; LorduPrinciple that = (LorduPrinciple) obj; return username.equals(that.username); } @Override public int hashCode() { return username.hashCode(); } }
3. 修改LoginModule,添加角色Principal
在LoLoginModule中扩展用户数据,增加角色字段,登录成功后将用户Principal和角色Principal同时添加到Subject:
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.login.FailedLoginException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import java.io.IOException; import java.util.Map; public class LoLoginModule implements LoginModule { // 扩展用户数据,新增角色列 public static final String[][] TEST_USERS = { {"admin", "password", "admin"}, {"admin2", "password2", "admin"}, {"user1", "userpass", "users"} }; private Subject subject = null; private CallbackHandler callbackHandler = null; private LorduPrinciple userPrincipal = null; private RolePrincipal rolePrincipal = null; @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { this.subject = subject; this.callbackHandler = callbackHandler; System.out.println("Lordu login module initialized...."); } @Override public boolean login() throws LoginException { boolean flag = false; System.out.println("Login..."); Callback[] callbackArray = new Callback[2]; callbackArray[0] = new NameCallback("User Name :"); callbackArray[1] = new PasswordCallback("Password: ", false); try { callbackHandler.handle(callbackArray); String username = ((NameCallback) callbackArray[0]).getName(); String password = new String(((PasswordCallback) callbackArray[1]).getPassword()); for (String[] user : TEST_USERS) { if (user[0].equals(username) && user[1].equals(password)) { userPrincipal = new LorduPrinciple(username); rolePrincipal = new RolePrincipal(user[2]); System.out.println("Authentication Success"); flag = true; break; } } if (!flag) throw new FailedLoginException("Authentication failure..."); } catch (IOException | javax.security.auth.callback.UnsupportedCallbackException e) { throw new LoginException(e.getMessage()); } return flag; } @Override public boolean commit() throws LoginException { boolean flag = false; System.out.println("Commit..."); if (subject != null) { if (!subject.getPrincipals().contains(userPrincipal)) { subject.getPrincipals().add(userPrincipal); } if (!subject.getPrincipals().contains(rolePrincipal)) { subject.getPrincipals().add(rolePrincipal); } flag = true; } return flag; } @Override public boolean abort() throws LoginException { System.out.println("Abort module implementing..."); if (subject != null) { if (userPrincipal != null && subject.getPrincipals().contains(userPrincipal)) { subject.getPrincipals().remove(userPrincipal); } if (rolePrincipal != null && subject.getPrincipals().contains(rolePrincipal)) { subject.getPrincipals().remove(rolePrincipal); } } subject = null; userPrincipal = null; rolePrincipal = null; return true; } @Override public boolean logout() throws LoginException { System.out.println("Logout module implementing...."); if (subject != null) { subject.getPrincipals().remove(userPrincipal); subject.getPrincipals().remove(rolePrincipal); } subject = null; return true; } }
4. 修正CallbackHandler类名匹配
原Driver中使用LoCallbackHandler,调整回调类实现为无参构造(从控制台读取输入):
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 java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; public class LoCallbackHandler implements CallbackHandler { @Override public void handle(Callback[] callbackArray) throws IOException, javax.security.auth.callback.UnsupportedCallbackException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); for (Callback callback : callbackArray) { if (callback instanceof NameCallback) { NameCallback nameCallback = (NameCallback) callback; System.out.print(nameCallback.getPrompt()); nameCallback.setName(br.readLine().trim()); } else if (callback instanceof PasswordCallback) { PasswordCallback passwordCallback = (PasswordCallback) callback; System.out.print(passwordCallback.getPrompt()); passwordCallback.setPassword(br.readLine().trim().toCharArray()); } else { throw new javax.security.auth.callback.UnsupportedCallbackException(callback, "Unsupported callback type"); } } } }
5. 在Driver中添加权限检查逻辑
修改performAction方法,执行操作前校验角色权限:
import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.security.Principal; public class Driver { public enum Action {action1, action2, logout}; public static void main(String[] args) { Driver driver = new Driver(); System.setProperty("java.security.auth.login.config", "Lord.config"); LoginContext loginContext = null; while (true) { try { loginContext = new LoginContext("LorduJaas", new LoCallbackHandler()); loginContext.login(); boolean flag = true; while (flag) { flag = driver.performAction(loginContext); } } catch (LoginException | IOException e) { System.out.println(e.getMessage()); } } } boolean performAction(LoginContext loginContext) throws IOException, LoginException { boolean flag = true; System.out.println("Please specify your action (Usage: action1,action2,logout)"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String actionInput = br.readLine().trim(); try { Action action = Action.valueOf(actionInput); Subject subject = loginContext.getSubject(); switch (action) { case logout: loginContext.logout(); System.out.println("Logged out"); flag = false; break; case action1: if (hasRole(subject, "admin")) { Subject.doAs(subject, (PrivilegedAction<Object>) () -> { System.out.println("Action 1 done.."); return null; }); System.out.println("By " + getUsername(subject)); } else { System.out.println("Permission denied: Only admin can perform action1"); } break; case action2: if (hasRole(subject, "admin") || hasRole(subject, "users")) { Subject.doAs(subject, (PrivilegedAction<Object>) () -> { System.out.println("Action 2 done.."); return null; }); System.out.println("By " + getUsername(subject)); } else { System.out.println("Permission denied: You don't have permission to perform action2"); } break; } } catch (IllegalArgumentException ex) { System.out.println("Invalid action: " + actionInput); } return flag; } // 检查Subject是否包含指定角色 private boolean hasRole(Subject subject, String roleName) { for (Principal p : subject.getPrincipals(RolePrincipal.class)) { if (p.getName().equals(roleName)) { return true; } } return false; } // 获取当前登录用户名 private String getUsername(Subject subject) { for (Principal p : subject.getPrincipals(LorduPrinciple.class)) { return p.getName(); } return "unknown"; } }
配置文件说明(Lord.config)
确保配置文件指向正确的LoginModule:
LorduJaas { 你的包名.LoLoginModule required; };
测试流程
- 用
admin/admin2登录,可执行所有操作(action1、action2、logout) - 用
user1登录,仅能执行action2和logout,执行action1会提示权限拒绝
内容的提问来源于stack exchange,提问作者hariharan baskaran
相关产品推荐
相关产品推荐

