什么是Principal
java.security.Principal,顾名思义和安全有关,但目前我们仅用其最最基本的含义,以后会进一步学习。
JAAS所使用的认证方案以两种非常重要的实体为基础:principal和subject。实际被认证的人或者服务称为subject。principal是一个惟一的实体,比如个人或者组的名字、帐号、社会安全号或者类似的惟一标识。为了惟一标识一个subject(这是认证的关键部分),一个或者多个principal必须与这个subject相关联。[1]
HttpServletRequest提供getUserPrincipal()来获取,其返回值通过HttpSerlvetRequestWrapper来设定。
在小例子中,我们将存放在session中的username放在请求的principal中,方便获取。
自定义所需的Principal
小例子中,用户的关键信息就是用户名。Pincipal是个接口,需要实现getName()。为了方便比对两个principal是否相同(本例就是用户名,而不是对象地址),重写了Object类的equals(为此重写了hashcode),我们还允许clone。public class UserPrincipal implements Principal, Cloneable, Serializable{
private static final long serialVersionUID = 1L; //这是Serializable要求给出,小例子不存在传输和读写在其他介质,可以不用Serializable。
//【1】小例子中最重要的身份信息是username,该信息一次性填入,不允许修改
private final String username;
public UserPrincipal(String username) {
this.username = username;
}
//【2】getName()是Principal接口,是主要使用的方法
@Override
public String getName() {
return this.username;
}
//【3】hashCode()和equals()即是Principal接口也是Object的接口,我们比对两个principal是否一直,不是比较对象的地址,而是里面的信息。同时修改toString()给出显示的信息
@Override
public int hashCode() {
return this.username.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof UserPrincipal
&& ((UserPrincipal) obj).username.equals(this.username);
}
@Override
public String toString() {
return this.username;
}
//【4】允许colon
@Override
protected UserPrincipal clone() throws CloneNotSupportedException {
return (UserPrincipal)super.clone();
}
//【5】提供两个静态方法getPrincipal()和setPrincipal(),将principal和session中的某个属性对应起来,也就是真实的仍借用session进行数据存放。放置在session,session过期后,将不存在
public static Principal getPrincipal(HttpSession session){
return session == null ? null :
(Principal)session.getAttribute("cn.wei.flowingflying.customer_support.user.principal");
}
public static void setPrincipal(HttpSession session, Principal principal){
session.setAttribute("cn.wei.flowingflying.customer_support.user.principal", principal);
}
}
小例子:用principal存放应用信息并进行auth
设置认证服务接口:AuthenticationService
public interface AuthenticationService {
/** 如果认证成功,返回principal对象,失败,返回null */
Principal authenticate(String username, String password);
}
设置认证控制器:login相关的Controller
@Controller
public class AuthenticationController {
@Inject private AuthenticationService authenticationService;
@RequestMapping(value="login",method=RequestMethod.GET)
public ModelAndView login(Map<String,Object> model,HttpSession session){
//【1】如果存在principal,说明已经登录了,进入主界面。通过filter的处理(见紧接),我们也可以通过request.getPrincipal()来获取该值。
if(UserPrincipal.getPrincipal(session) != null)
return getHome();
model.put("loginFailed", false);
model.put("loginForm",new Form());
return new ModelAndView("login");
}
@RequestMapping(value = "login", method = RequestMethod.POST)
public ModelAndView login(Map<String,Object> model,HttpSession session,HttpServletRequest request,Form form){
//【2】进行身份校验,获取principal
Principal principal = this.authenticationService.authenticate(form.getUsername(), form.getPassword());
// 2.1)如果校验失败
if(principal == null){
logger.warn("Login failed for user {}",form.getUsername());
form.setPassword(null);
model.put("loginFailed", false);
model.put("loginForm", form);
return new ModelAndView("login");
}
// 2.2)如果校验成功,设置principal
UserPrincipal.setPrincipal(session, principal);
request.changeSessionId();
return getHome();
}
......
}
允许request.getUserPrincipal()获取:AuthenticationFilter
AuthenticationFilter有两个作用:
- 我们应对每个HTTP请求(除了静态资源,如图片,css文件)进行用户身份认证检查,采用Filter的方式。
- 封装request,支持request.getUserPrincipal()操作。
public class AuthenticationFilter implements Filter {
... ...
public void doFilter(ServletRequest request,ServletResponse response, FilterChain chain) throws IOException,ServletException{
//【1】获取principal
HttpSession session = ((HttpServletRequest)request).getSession(false);
final Principal principal = UserPrincipal.getPrincipal(session);
//【2】如果principal为null(未登录),进入登录界面
if(principal == null){
((HttpServletResponse)response).sendRedirect(
((HttpServletRequest)request).getContextPath() + "/login");
}else{
//【3】如果principal存在(已登录),我们希望能从request中直接获取principal信息。通过wrapper request,给出getUserPrincipal()的返回
chain.doFilter(
new HttpServletRequestWrapper((HttpServletRequest)request){
@Override
public Principal getUserPrincipal() {
return principal;
}
},response);
}
}
}
在LoggingFilter中使用principal
我们为了让log4j2在log中%X{username}给出登录用户名,写了LoggingFilter,在里面通过principal获取。在BootStrap中LoggingFilter是位于AuthenticationFilter之前,所有采用request.getUserPrincipal()没有效果,需要通过UserPrincipal的静态方法来获取。
public class LoggingFilter implements Filter {
... ...
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 如果LoggingFilter在AuthenticationFilter之前先执行,则无法使用request.getPrincipal()的处理。然而我们提供了从session中获取的静态方法,可以方便获得
Principal principal = UserPrincipal.getPrincipal(((HttpServletRequest)request).getSession(false));
if(principal != null){
ThreadContext.put("username", principal.getName());
}
try{
chain.doFilter(request, response);
}finally{
ThreadContext.clearAll();
}
}
}