一、目录
- 用户表的设计
- 重定向和转发
- shiro框架初探
二、实验结果和学习内容
用户表的设计
一开始,用于测试的时候,用户表就是简单的username加password两个字段,而为了后续的功能实现,我需要增加一个userID,并且尽量使得不能存在溢出的可能(当作亿级用户量来处理)。并且如果应用支持第三方登录,或者只使用第三方登录,那么第三方的 User ID 有可能是 64 位的。虽然自己的应用不一定能增长到超过 32 位,但是第三方很可能是 64 位的。
然后还要增加用户状态 status
判断用户是否被封禁,注册时间和注册IP地址、上次登录时间和IP地址备查(并衍生出登录记录表,用来判断是否异地登录等,在此不表),用户角色/权限 role (又衍生出用户角色权限关系),业务也需要个人的个人信息如真实姓名、地址等也要往上添加,现在形成了一个很完整的用户关系表。
在这之后,微博开放了第三方网站登录,用微博帐号就能登录。那就要增加个微博用户登录表,并且和本来的用户表关联,则这个微博用户信息表如下:
id 自增ID
user_id 关联本站用户ID
uid 微博唯一ID
access_token
access_expire
不仅如此,算上QQ和微信,一下子要接入好多家第三方登录了,只能就着“微博用户信息表”继续加类型加判断,如果是每个第三方登录都新建一个表,肯定会很冗杂。
而作为iOS应用,必须要支持手机号登录。所以表结构如下:
用户表
id
username
email
phone
…
用户第三方登录表
id
user_id
app_type
app_user_id
access_token
进行改进
无论username+password,还是phone+password,都是一种用户信息+密码的验证形式;再来理解第三方登录,其实它也是用户信息+密码的形式,用户信息即第三方系统中的ID(第三方登录一定会给一个在他们系统中的唯一标识),密码即access_token,只不过是一种有使用时效定期修改的密码。所以我们把它抽象出了用户基础信息表加上用户授权信息表的形式。
用户基础信息表 users
id
nickname
avatar
用户授权信息表 user_auths
id
user_id
identity_type 登录类型(手机号 邮箱 用户名)或第三方应用名称(微信 微博等)
identifier 标识(手机号 邮箱 用户名或第三方应用的唯一标识)
credential 密码凭证(站内的保存密码,站外的不保存或保存token)
这个系统最大的特色就是,用户信息表不保存任何密码,不保存任何登录信息(如用户名、手机号、邮箱),只留有昵称、头像等基础信息。所有和授权相关(且基本前端展示无关的),都放在用户信息授权表,用户信息表和用户授权表是一对多的关系。
例如:
users
id | nickname | avatar |
---|---|---|
1 | 慕容雪村 | http://…/avatar.jpg |
2 | 魔力鸟 | http://…/avatar2.jpg |
3 | 科比 | http://…/avatar3.jpg |
user_auths
id | user_id | identity_type | identifier | credential |
---|---|---|---|---|
1 | 1 | 123@example.com | password_hash(密码) | |
2 | 1 | phone | 13888888888 | password_hash(密码) |
3 | 1 | 微博UID | 微博access_token | |
4 | 2 | username | moliniao | password_hash(密码) |
5 | 3 | weixin | 微信UserName | 微信token |
具体处理如下,当用户发来邮箱/用户名/手机号和密码请求登录的时候,依然是先判断类型,以某用户使用了手机号登录为例,使用 SELECT * FROM user_auths WHERE type=’phone’ and identifier=’手机号’ 查找条目,如有,取出并判断password_hash(密码)是否和该条目的credential相符,相符则通过验证,随后通过user_id获取用户信息。
如果使用第三方登录,则只要判断 SELECT * FROM user_auths WHERE type=’weixin’ and identifier=’微信UserName’ ,如果有记录,则直接登录成功,使用新的token更新原token。假设与微信服务器通信不被劫持的情况下无需判断凭证问题。
--
重定向和转发
然后由于功能实现需要用到转发或者重定向的功能,因此了解了一下:
1.使用HttpServletResponse 重定向到另一个视图
@RequestMapping("/resp")
public void handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
resp.sendRedirect("index.jsp");
}
2.使用HttpServletRequest 转发(默认访问/下的index.jsp页面 不受渲染器的影响)
@RequestMapping("/resp")
public void handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
req.setAttribute("message","it's forword ");
req.getRequestDispatcher("index.jsp").forward(req,resp);
}
3.直接返回jsp页面的名称(无渲染器)
@RequestMapping("/nice")
public String hello1(){
//转发方式1
return "home.jsp";
//转发方式2
return "forward:index.jsp";
//重定向方式
return "redirect:index.jsp";
}
4.当有渲染器指定的时候
@RequestMapping("/nice")
public String hello1(){
//转发方式1
return "home";
//转发方式2
return "forward:index";
//重定向方式 hello指的是requsrmapping
return "redirect:hello";
}
5.使用ModelAndView
需要视图解析器 能指定跳转页面
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest,
javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mv = new ModelAndView();
//封装要显示到视图的数据
mv.addObject("msg","hello myfirst mvc");
//视图名
mv.setViewName("hello");
return mv;
}
}
需要进行配置:
<!--配置渲染器-->
<!--配置hellocontroller中页面的位置-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<!--结果视图的前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--结果视图的后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<bean name="/hello.do" class="com.jsu.mvc.HelloController"></bean>
6.使用ModelView
//通过modelmap方式
@RequestMapping("/modelmap")
public String modelHello(String name,ModelMap map){
map.addAttribute("name",name);
System.out.println(name);
return "index.jsp";
}
并且重定向和转发的具体效果都是跳转到别的页面,但实际上两者的区别不尽相同。
sendRedirect()
是在用户的浏览器端工作,同时它可以重定向至不同的主机上,sendRedirect()
可以重定向有frame的jsp文件。
假设转发代码包含于注册的servlet-url
为/ggg/tt
;jsp为/ggg/tt.jsp
。
绝对路径:response.sendRedirect("http://www.brainysoftware.com")
发送至http://www.brainysoftware.com
根路径:response.sendRedirect("/ooo")
发送至http://localhost:8080/ooo
相对路径:response.sendRedirect("ooo")
发送至http://localhost:8080/Test/ggg/ooo
。
forward()
无法重定向至有frame的jsp文件,可以重定向至有frame的html文件,
只有在客户端没有输出时才可以调用forward方法。如果当前页面的缓冲区(buffer)不是空的,那么你在调用forward方法前必须先清空缓冲区。
"/"代表相对与web应用路径
RequestDispatcher rd = request.getRequestDispatcher("/ooo");
rd.forward(request, response);
//提交至http://localhost:8080/Test/ooo
RequestDispatcher rd = getServletContext().getRequestDispatcher("/ooo");
rd.forward(request, response);
//提交至http://localhost:8080/Test/ooo
RequestDispatcher rd =getServletContext().getNamedDispatcher("TestServlet");
//(TestServlet为一个<servlet-name>)
rd.forward(request, response);
//提交至名为TestServlet的servlet
而转发和重定向也叫直接请求转发(Forward)和间接请求转发(Redirect)
直接转发方式(Forward),客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每个信息资源是共享的。
间接转发方式(Redirect)实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。
shiro框架初探
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
关键对象:
Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
shiro能做什么?
认证:验证用户的身份
授权:对用户执行访问控制:判断用户是否被允许做某事
会话管理:在任何环境下使用 Session API,即使没有 Web 或EJB 容器。
加密:以更简洁易用的方式使用加密功能,保护或隐藏数据防止被偷窥
Realms:聚集一个或多个用户安全数据的数据源
单点登录(SSO)功能。
为没有关联到登录的用户启用 "Remember Me“ 服务
Shiro 的四大核心部分
Authentication(身份验证):简称为“登录”,即证明用户是谁。
Authorization(授权):访问控制的过程,即决定是否有权限去访问受保护的资源。
Session Management(会话管理):管理用户特定的会话,即使在非 Web 或 EJB 应用程序。
Cryptography(加密):通过使用加密算法保持数据安全
shiro的三个核心组件:
Subject :正与系统进行交互的人,或某一个第三方服务。所有 Subject 实例都被绑定到(且这是必须的)一个SecurityManager 上。
SecurityManager:Shiro 架构的心脏,用来协调内部各安全组件,管理内部组件实例,并通过它来提供安全管理的各种服务。当 Shiro 与一个 Subject 进行交互时,实质上是幕后的 SecurityManager 处理所有繁重的 Subject 安全操作。
Realms :本质上是一个特定安全的 DAO。当配置 Shiro 时,必须指定至少一个 Realm 用来进行身份验证和/或授权。Shiro 提供了多种可用的 Realms 来获取安全相关的数据。如关系数据库(JDBC),INI 及属性文件等。可以定义自己 Realm 实现来代表自定义的数据源。
三、实验思考及感想
系统正在逐步完善,当然问题也出现了,就是了解的东西实在有限,需要查阅很多资料,而且需要不停的踩坑,因为网上很多的资料都是存在问题的,或者说有很多虎头蛇尾的内容,例如在查阅controller入参和返回类型的时候,发现存在返回Map类型,有点意料之外的感觉。并且配置方面因为不是特别深入地了解,只能慢慢摸索,发现想要测试的时候,无法获取js文件,即除了jsp外的静态资源,就需要进行相应的配置,并进行测验。后续会完成shiro框架的加入,以及尝试完成用户头像的上传和收藏列表的实现