有的时候我不禁想,如果从 Spring Security 出身的第一天开始,我们就一贯在追踪它,那么本日再去看它的源码一定很大略,由于我们理解到每一行代码的缘由。

然而事实上我们大部分人都是中途打仗到它的,包括松哥自己。
以是在阅读源码的时候,有时候会碰着一些不是那么随意马虎理解的东西,并不是说这个有多难,只是我们不理解 N 年前的开拓环境,因此也就不随意马虎理解某一行代码涌现的意义。

所以为了搞透彻这个框架,有时候我们还得去理解之前发生了什么。

jsp中怎么获取登录的用户名和密码写了这么多年月码如许的登录方法照样头一回见 Ruby

这就跟学 Spring Boot 一样,很多小伙伴问要不要跳过 SSM ,我说不要,乃至还专门写了一篇文章(Spring Boot 要怎么学?要学哪些东西?要不要先学 SSM?),跳过了 SSM ,Spring Boot 中的很多东西就无法真正理解。

扯远了。


Spring Security 中对 HttpServletRequest 要求进行了封装,重写了 HttpServletRequest 中的几个和安全管理干系的方法,想要理解 Spring Security 中的重写,就要先从 HttpServletRequest 开始看起。

有小伙伴可能会说,HttpServletRequest 能跟安全管理扯上什么关系?本日松哥就来和大家捋一捋,我们不讲 Spring Security,就来纯挚讲讲 HttpServletRequest 中的安全管理方法。

1.HttpServletRequest

在 HttpServletRequest 中,我们常用的方法如:

public String getHeader(String name);public String getParameter(String name);public ServletInputStream getInputStream()...

这些常见的方法可能大家都有用过,还有一些不常见的,和安全干系的方法:

publicStringgetRemoteUser();publicbooleanisUserInRole(Stringrole);publicjava.security.PrincipalgetUserPrincipal();publicbooleanauthenticate(HttpServletResponseresponse)throwsIOException,ServletException;publicvoidlogin(Stringusername,Stringpassword)throwsServletException;publicvoidlogout()throwsServletException;

前面三个方法,在之前的 Servlet 中就有,后面三个方法,则是从 Servlet3.0 开始新增加的方法。
从方法名上就可以看出,这些都是和认证干系的方法,但是这些方法,我估计很多小伙伴都没用过,由于不太实用。

在 Spring Security 框架中,对这些方法进行了重写,进而带来了一些好玩并且方便的特性,这个松哥在后面的文章中再和大家分享。

要理解 Spring Security 中的封装,就得先来看看,不用框架,这些方法该怎么用!

2.实践出真知

我们创建一个通俗俗通的 Web 项目,不该用任何框架(后面的案例都基于此),然后在 doGet 方法中打印出 HttpServletRequest 的类型,代码如下:

@OverrideprotectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresp)throwsServletException,IOException{System.out.println("request.getClass()="+request.getClass());}

代码运行打印结果如下:

request.getClass()=classorg.apache.catalina.connector.RequestFacade

HttpServletRequest 是一个接口,而 RequestFacade 则是一个正儿八经的 class。

HttpServletRequest 是 Servlet 规范中定义的 ServletRequest,这相称于是标准的 Request;但是在 Tomcat 中的 Request 则是 Tomcat 自己自定义的 Request,自定义的 Request 实现了 HttpServletRequest 接口并且还定义了很多自己的方法,这些方法还是 public 的,如果直策应用 Tomcat 自定义的 Request,开拓者只须要向下转型就能调用这些 Tomcat 内部方法,这是有问题的,以是又用 RequestFacade 封装了一下,以至于我们实际上用到的便是 RequestFacade 工具。

那么毫无疑问,HttpServletRequest#login 方法详细实现便是在 Tomcat 的 Request#login 方法中完成的。
经由源码追踪,我们创造,登录的数据源是由 Tomcat 中的 Realm 供应的,把稳这个 Realm 不是 Shiro 中的 Realm。

Tomcat 中供应了 6 种 Realm,可以支持与各种数据源的对接:

JDBCRealm:很明显,这个 Realm 可以对接到数据库中的用户信息。
DataSourceRealm:它通过一个 JNDI 命名的 JDBC 数据源在关系型数据库中查找用户。
JNDIRealm:通过一个 JNDI 供应者1在 LDAP 目录做事器中查找用户。
UserDatabaseRealm:这个数据源在 Tomcat 的配置文件中 conf/tomcat-users.xml。
MemoryRealm:这个数据源是在内存中,内存中的数据也是从 conf/tomcat-users.xml 配置文件中加载的。
JAASRealm:JAAS 架构来实现对用户身份的验证。

如果这些 Realm 无法知足需求,当然我们也可以自定义 Realm,只不过一样平常我们不这样做,为啥?由于这这种登录办法用的太少了!
本日这篇文章纯粹是和小伙伴们开开眼界。

如果自定义 Realm 的话,我们只须要实现 org.apache.catalina.Realm 接口,然后将编译好的 jar 放到 $CATALINA_HOME/lib 下即可,详细的配置则和下面先容的同等。

接下来我和大家先容两种配置办法,一个是 UserDatabaseRealm,另一个是 JDBCRealm。

2.1 基于配置文件登录

我们先来定义一个 LoginServlet:

@WebServlet(urlPatterns="/login")publicclassLoginServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{Stringusername=req.getParameter("username");Stringpassword=req.getParameter("password");try{req.login(username,password);}catch(ServletExceptione){req.getRequestDispatcher("/login.jsp").forward(req,resp);return;}booleanlogin=req.getUserPrincipal()!=null&&req.isUserInRole("admin");if(login){resp.sendRedirect("/hello");return;}else{req.getRequestDispatcher("/login.jsp").forward(req,resp);}}}

当要求到达后,先提取出用户名和密码,然后调用 req.login 方法进行登录,如果登录失落败,则跳转到登录页面。

登录完成后,通过获取登任命户信息以及判断登任命户角色,来确保用户是否登录成功。

如果登录成功,就跳转到项目运用首页,否则就跳转到登录页面。

接下来定义 HelloServlet:

@WebServlet(urlPatterns="/hello")publicclassHelloServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{PrincipaluserPrincipal=req.getUserPrincipal();if(userPrincipal==null){resp.setStatus(401);resp.getWriter().write("pleaselogin");}elseif(!req.isUserInRole("admin")){resp.setStatus(403);resp.getWriter().write("forbidden");}else{resp.getWriter().write("hello");}}}

在 HelloServlet 中,先判断用户是否已经登录,没登录的话,就返回 401,已经登录但是不具备相应的角色,就返回 403,否则就返回 hello。

接下来再定义 LogoutServlet,实行注销操作:

@WebServlet(urlPatterns="/logout")publicclassLogoutServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{req.logout();resp.sendRedirect("/hello");}}

logout 方法也是 HttpServletRequest 自带的。

末了再大略定义一个 login.jsp 页面,如下:

<%@pagecontentType="text/html;charset=UTF-8"language="java"%&gt;<html><head><title>Title</title></head><body><formaction="/login"method="post"><inputtype="text"name="username"><inputtype="text"name="password"><inputtype="submit"value="登录"></form></body></html>

所有事情都准备好了,接下来便是数据源了,默认情形下加载的是 conf/tomcat-users.xml 中的数据,找到 Tomcat 的这个配置文件,修正之后内容如下:

<?xmlversion="1.0"encoding="UTF-8"?><tomcat-users><rolerolename="admin"/><userusername="javaboy"password="123"roles="admin"/></tomcat-users>

配置完成后,启动项目进行测试。
登任命户名是 javaboy,登录密码是 123,详细的测试过程我就不再演示了。

2.2 基于数据库登录

如果想基于数据库登录,我们须要先准备好数据库和表,须要两张表,user 表和 role 表,如下:

CREATETABLE`user`(`id`int(11)unsignedNOTNULLAUTO_INCREMENT,`username`varchar(255)COLLATEutf8mb4_unicode_ciDEFAULTNULL,`password`varchar(255)COLLATEutf8mb4_unicode_ciDEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=2DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_unicode_ci;CREATETABLE`role`(`id`int(11)unsignedNOTNULLAUTO_INCREMENT,`username`varchar(255)COLLATEutf8mb4_unicode_ciDEFAULTNULL,`role_name`varchar(255)COLLATEutf8mb4_unicode_ciDEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=2DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_unicode_ci;

然后向表中添加两行仿照数据:

接下来,找到 Tomcat 的 conf/server.xml 文件,修正配置,如下:

<RealmclassName="org.apache.catalina.realm.LockOutRealm"><RealmclassName="org.apache.catalina.realm.JDBCRealm"debug="99"driverName="com.mysql.jdbc.Driver"connectionURL="jdbc:mysql://localhost:3306/basiclogin"connectionName="root"connectionPassword="123"userTable="user"userNameCol="username"userCredCol="password"userRoleTable="role"roleNameCol="role_name"/></Realm>

在这段配置中:

指定 JDBCRealm。
指天命据库驱动。
指天命据库连接地址。
指天命据库连接用户名/密码。
指定用户表名称;用户名的字段名以及密码字段名。
指定角色表名称;以及角色字段名。

配置完成后,再次登录测试,此时的登录数据便是来自数据库的数据了。

3.优化

前面的 HelloServlet,我们是在代码中手动配置的,假如每个 Servlet 都这样配置,这要搞到猴年马月了~

以是我们对此可以在 web.xml 中进行手动配置。

首先我们创建一个 AdminServlet 进行测试,如下:

@WebServlet(urlPatterns="/admin/hello")publicclassAdminServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{resp.getWriter().write("helloadmin!");}}

然后在 web.xml 中进行配置:

<security-constraint><web-resource-collection><web-resource-name>admin</web-resource-name><url-pattern>/admin/</url-pattern></web-resource-collection><auth-constraint><role-name>admin</role-name></auth-constraint></security-constraint><security-role><role-name>admin</role-name></security-role>

这个配置表示 /admin/ 格式的要求路径,都须要具有 admin 角色才能访问,否则就访问不到,这样,每一个 Admin 干系的 Servlet 就被保护起来了,不用在 Servlet 中写代码判断了。

4.小结

好啦,经由本文的先容,相信小伙伴们对付 HttpServletRequest 中关于认证的几个方法基本上都理解了,接下来的文章松哥将连续和大家先容这些方法在 Spring Security 框架中是如何进行蜕变的,看懂了本文,后面的文章就很好理解了~

本文案例下载地址:https://github.com/lenve/javaboy-code-samples

好啦,小伙伴们如果以为有收成,记得点个在看鼓励下松哥哦~