一个web工程,如果涉及到私人信息或不可公开的资源,则必然要对访问者做过滤和认证,访问者只能获取跟自己相关或有权限访问的信息,这就是我们所熟知的登陆。简单的登陆通常是这样实现的:提供一个登陆接口,和一个过滤器。登陆接口用来用户名和密码校验,并将用户信息保存在http session中。过滤器则用来拦截所有未经授权的访问。
@RestController
@RequestMapping("/login")
public class LoginController {
public void login(HttpServletRequest request, HttpServletResponse response) {
String json;
PrintWriter out = response.getWriter();
String user = request.getParameter("usr");
String pwd= request.getParameter("password");
if (pwd.equals(getPasswordByUserName(user))) {
request.getSession().setAttribute("user", user);
json = "{\"success\":true}";
}
else {
json = "{\"success\":false}";
}
out.print(json);
out.flush;
out.close;
}
}
public class AuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (!httpServletRequest.getServletPath().equals("/login")) {
HttpSession session = ((HttpServletRequest) request).getSession(false);
if (session == null || session.getAttribute("user") == null) {
((HttpServletResponse) response).setStatus(401);
return;
}
}
chain.doFilter(request, response);
}
}
Spring security 作为一个强大且可高度定制化的认证和访问控制框架,是安全化 spring 应用的实际标准。它为Java应用提供认证和授权的功能,用户能够轻易地扩展以适应自己的需求。
Spring security 认证的过程
- 用户向url /login Post登陆请求,该请求从由loginPage指定的前台页面发送,携带表单参数username和password。(/login 是spring security 4 默认拦截的认证url,它并不指向任何物理资源,在spring security 4之前,该url是/j_spring_security_check,参数是j_username和j_password;可以通过login-processing-url自定义认证url。事实上,我们还可以绕过鉴权过滤器,直接向自定义的登陆rest接口post登陆请求,只要我们引入了spring security相关的包,所有认证的逻辑将会在request.login(username, password)里完成,如下所示)。
@RequestMapping(value = "/my_login", method = RequestMethod.POST) public void login(@RequestParam("username") String username, @RequestParam("password") String password, HttpServletRequest request, HttpServletResponse response) throws DmsException { try { request.login(username, password); } catch (ServletException e) { throw new MyException("0000", e); } request.getSession(); }
- 根据传入的 username 和 password 生成 UsernamePasswordAuthenticationToken,AuthenticationManager接口的 authenticate 方法将对生成的Authentication进行认证处理。AuthenticationManager的默认实现类是ProviderManager,ProviderManager将认证委托给AuthenticatiionProvider处理。认证的本质是将Authentication和UserDetails中的用户信息进行比较。UserDetails包装的是拥有权限的用户信息,通常是从数据库中获取。
- UsernamePasswordAuthenticationFilter 过滤器在登陆成功后会通过SecurityContextHolder.getContext().setAuthentication() 将认证信息Authentication绑定到SecurityContext。
- 下一次请求时,过滤器链头的 SecurityContextPersistenceFilter 会从 Session 中获取已登陆用户信息并生成 Authentication,并将 认证信息对象绑定到 SecurityContext。如果是对需要权限的接口或资源的请求,Spring Security将从SecurityContext中获取用户的权限来判定是否可以访问该接口或资源。
Spring Security的核心组件
-
SecurityContextHolder,提供了一系列静态方法,代理一个SecurityContextHolderStrategy实例,方便用户对安全上下文的访问和设置。其初始化方法在静态块中被调用static { initialize(); } private static void initialize() { if (!StringUtils.hasText(strategyName)) { // Set default // 如果未设置strategyName属性,则默认使用ThreadLocal策略 strategyName = MODE_THREADLOCAL; } if (strategyName.equals(MODE_THREADLOCAL)) { // 线程局部变量安全持有策略,每个线程分别持有各自的SecurityContext strategy = new ThreadLocalSecurityContextHolderStrategy(); } else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) { // 可继承线程局部变量安全持有策略,每新起一个线程,则从父线程继承SecurityContext strategy = new InheritableThreadLocalSecurityContextHolderStrategy(); } else if (strategyName.equals(MODE_GLOBAL)) { // 全局安全上下文持有策略,所有的实例都共享同一个SecurityContext // 适用于富客户端应用,如swing应用。 strategy = new GlobalSecurityContextHolderStrategy(); } else { // Try to load a custom strategy // 用户可以自定义一个SecurityContextHolderStrategy实现类 try { Class<?> clazz = Class.forName(strategyName); Constructor<?> customStrategy = clazz.getConstructor(); strategy = (SecurityContextHolderStrategy) customStrategy.newInstance(); } catch (Exception e