什么是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有两个作用:

  1. 我们应对每个HTTP请求(除了静态资源,如图片,css文件)进行用户身份认证检查,采用Filter的方式。
  2. 封装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();
       }
    }
}

相关链接: 我的Professional Java for Web Applications相关文章