目录
shiro 介绍
shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。spring中有spring security (原名Acegi),是一个权限框架,它和spring依赖过于紧密,没有shiro使用简单。shiro不依赖于spring,shiro不仅可以实现 web应用的权限管理,还可以实现c/s系统,分布式系统权限管理,shiro属于轻量框架,越来越多企业项目开始使用shiro。
shiro 名词解释
subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。
securityManager:安全管理器,主体进行认证和授权都 是通过securityManager进行。它包含下面的认证器和授权器。
authenticator:认证器,主体进行认证最终通过authenticator进行的。
authorizer:授权器,主体进行授权最终通过authorizer进行的。
sessionManager:web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。可以实现单点登录。
SessionDao: 通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
cache Manager:缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
realm:域,领域,相当于数据源,通过realm存取认证、授权相关数据。(它的主要目的是与数据库打交道,查询数据库中的认证的信息(比如用户名和密码),查询授权的信息(比如权限的code等,所以这里可以理解为调用数据库查询一系列的信息,一般情况下在项目中采用自定义的realm,因为不同的业务需求不一样))
注意:在realm中存储授权和认证的逻辑。
cryptography:密码管理,提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。比如 md5散列算法
shiro 框架结构
认证过程
1、通过ini配置文件创建securityManager
2、调用subject.login方法主体提交认证,提交的token
3、securityManager进行认证,securityManager最终由ModularRealmAuthenticator进行认证。
4、ModularRealmAuthenticator调用IniRealm(给realm传入token) 去ini配置文件中查询用户信息
5、IniRealm根据输入的token(UsernamePasswordToken)从 shiro.ini查询用户信息,根据账号查询用户信息(账号和密码)
- 如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码)
- 如果查询不到,就给ModularRealmAuthenticator返回null
6、ModularRealmAuthenticator接收IniRealm返回Authentication认证信息
授权流程
- 如果返回的认证信息是null,ModularRealmAuthenticator抛出异常(org.apache.shiro.authc.UnknownAccountException)
- 如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在) 和 token中的密码 进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)
1、对subject进行授权,调用方法isPermitted("permission串")
2、SecurityManager执行授权,通过ModularRealmAuthorizer执行授权
3、ModularRealmAuthorizer执行realm(自定义的Realm)从数据库查询权限数据,调用realm的授权方法:doGetAuthorizationInfo
4、realm从数据库查询权限数据,返回ModularRealmAuthorizer
5、ModularRealmAuthorizer调用PermissionResolver进行权限串比对
6、如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。
与spring boot 整合(转)
目录结构
POM依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
<!--<version>3.0.1.RELEASE</version>-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
Application.yml
server:
servlet:
context-path: /
port: 80
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/crmData?characterEncoding=utf8&useSSL=false
username: root
password: root
jpa:
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #按字段名字建表
show-sql: true
database: mysql
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
thymeleaf:
cache: false
messages:
basename: myconfig
SQL脚本
INSERT INTO `user` (`userId`,`username`,`name`,`password`,`salt`,`state`)
VALUES ('1', 'admin', '管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
INSERT INTO `syspermission` (`permissionId`,`available`,`permissionname`,`parentid`,`parentids`,`permission`,`resourcetype`,`url`)
VALUES (1,0,'用户管理',0,'0/','user:view','menu','user/userList');
INSERT INTO `syspermission` (`permissionId`,`available`,`permissionname`,`parentid`,`parentids`,`permission`,`resourcetype`,`url`)
VALUES (2,0,'用户添加',1,'0/1','user:add','button','user/userAdd');
INSERT INTO `syspermission` (`permissionId`,`available`,`permissionname`,`parentid`,`parentids`,`permission`,`resourcetype`,`url`)
VALUES (3,0,'用户删除',1,'0/1','user:del','button','user/userDel');
INSERT INTO `sysrole` (`roleid`,`available`,`description`,`role`) VALUES (1,0,'管理员','admin');
INSERT INTO `sysrole` (`roleid`,`available`,`description`,`role`) VALUES (2,0,'VIP会员','vip');
INSERT INTO `sysrole` (`roleid`,`available`,`description`,`role`) VALUES (3,1,'test','test');
INSERT INTO `sysrolepermission` (`permissionid`,`roleid`) VALUES (1,1);
INSERT INTO `sysrolepermission` (`permissionid`,`roleid`) VALUES (2,1);
INSERT INTO `sysrolepermission` (`permissionid`,`roleid`) VALUES (3,2);
INSERT INTO `sysuserrole` (`roleid`,`userId`) VALUES (1,1);
注意admin的密码是123456,这里保存的是加密后的密码,根据前面的设置,是md5,散列2次。
登录的时候shiro会根据配置自动给密码123456加密,然后与数据库里取出的密码比对。
注意先运行一遍程序,JPA生成数据库表后,手工执行sql脚本插入样本数据。
实体
实体有三个,根据规则会自动生成两个中间表,数据库实际上会生成5张表。
分别是User,SysRole,SysPermission,中间表按照@JoinTable来生成。
@Entity
public class User {
@Id
@GenericGenerator(name="generator",strategy = "native")
@GeneratedValue(generator = "generator")
private Integer userId;
@Column(nullable = false, unique = true)
private String userName; //登录用户名
@Column(nullable = false)
private String name;//名称(昵称或者真实姓名,根据实际情况定义)
@Column(nullable = false)
private String password;
private String salt;//加密密码的盐
private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
@ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据;
@JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "userId") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
private List<SysRole> roleList;// 一个用户具有多个角色
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime createTime;//创建时间
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate expiredDate;//过期日期
private String email;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDate getExpiredDate() {
return expiredDate;
}
public void setExpiredDate(LocalDate expiredDate) {
this.expiredDate = expiredDate;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public byte getState() {
return state;
}
public void setState(byte state) {
this.state = state;
}
public List<SysRole> getRoleList() {
return roleList;
}
public void setRoleList(List<SysRole> roleList) {
this.roleList = roleList;
}
/**
* 密码盐. 重新对盐重新进行了定义,用户名+salt,这样就不容易被破解,可以采用多种方式定义加盐
* @return
*/
public String getCredentialsSalt(){
return this.userName+this.salt;
}
}
@Entity
public class SysRole {
@Id
@GenericGenerator(name="generator",strategy = "native")
@GeneratedValue(generator = "generator")
private Integer roleId; // 编号
@Column(nullable = false, unique = true)
private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
private String description; // 角色描述,UI界面显示使用
private Boolean available = Boolean.TRUE; // 是否可用,如果不可用将不会添加给用户
//角色 -- 权限关系:多对多关系;
@ManyToMany(fetch= FetchType.EAGER)
@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
private List<SysPermission> permissions;
// 用户 - 角色关系定义;
@ManyToMany
@JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="userId")})
private List<User> users;// 一个角色对应多个用户
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
public List<SysPermission> getPermissions() {
return permissions;
}
public void setPermissions(List<SysPermission> permissions) {
this.permissions = permissions;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
@Entity
public class SysPermission {
@Id
@GenericGenerator(name="generator",strategy = "native")
@GeneratedValue(generator = "generator")
private Integer permissionId;//主键.
@Column(nullable = false)
private String permissionName;//名称.
@Column(columnDefinition="enum('menu','button')")
private String resourceType;//资源类型,[menu|button]
private String url;//资源路径.
private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
private Long parentId; //父编号
private String parentIds; //父编号列表
private Boolean available = Boolean.TRUE;
//角色 -- 权限关系:多对多关系;
@ManyToMany
@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
private List<SysRole> roles;
public Integer getPermissionId() {
return permissionId;
}
public void setPermissionId(Integer permissionId) {
this.permissionId = permissionId;
}
public String getPermissionName() {
return permissionName;
}
public void setPermissionName(String permissionName) {
this.permissionName = permissionName;
}
public String getResourceType() {
return resourceType;
}
public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getParentIds() {
return parentIds;
}
public void setParentIds(String parentIds) {
this.parentIds = parentIds;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
}
DAO,这里用JPA
public interface UserRepository extends JpaRepository<User,Integer> {
User findByUserName(String userName);
}
Service
public interface UserService {
User findByUserName(String userName);
}
public interface LoginService {
LoginResult login(String userName,String password);
void logout();
}
Service.impl
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserRepository userRepository;
@Override
public User findByUserName(String userName) {
return userRepository.findByUserName(userName);
}
}
//内部使用的一个model,根据需要扩展
public class LoginResult {
private boolean isLogin = false;
private String result;
public boolean isLogin() {
return isLogin;
}
public void setLogin(boolean login) {
isLogin = login;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}
@Service
public class LoginServiceImpl implements LoginService {
@Override
public LoginResult login(String userName, String password) {
LoginResult loginResult = new LoginResult();
if(userName==null || userName.isEmpty())
{
loginResult.setLogin(false);
loginResult.setResult("用户名为空");
return loginResult;
}
String msg="";
// 1、获取Subject实例对象
Subject currentUser = SecurityUtils.getSubject();
// // 2、判断当前用户是否登录
// if (currentUser.isAuthenticated() == false) {
//
// }
// 3、将用户名和密码封装到UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
// 4、认证
try {
currentUser.login(token);// 传到MyAuthorizingRealm类中的方法进行认证
Session session = currentUser.getSession();
session.setAttribute("userName", userName);
loginResult.setLogin(true);
return loginResult;
//return "/index";
}catch (UnknownAccountException e)
{
e.printStackTrace();
msg = "UnknownAccountException -- > 账号不存在:";
}
catch (IncorrectCredentialsException e)
{
msg = "IncorrectCredentialsException -- > 密码不正确:";
}
catch (AuthenticationException e) {
e.printStackTrace();
msg="用户验证失败";
}
loginResult.setLogin(false);
loginResult.setResult(msg);
return loginResult;
}
@Override
public void logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
}
}
config,配置类
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private UserService userService;
//权限信息,包括角色以及权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//如果身份认证的时候没有传入User对象,这里只能取到userName
//也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象
User user = (User)principals.getPrimaryPrincipal();
for(SysRole role:user.getRoleList()){
authorizationInfo.addRole(role.getRole());
for(SysPermission p:role.getPermissions()){
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
/*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
//获取用户的输入的账号.
String userName = (String)token.getPrincipal();
System.out.println(token.getCredentials());
//通过username从数据库中查找 User对象.
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
User user = userService.findByUserName(userName);
System.out.println("----->>user="+user);
if(user == null){
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //这里传入的是user对象,比对的是用户名,直接传入用户名也没错,但是在授权部分就需要自己重新从数据库里取权限
user.getPassword(), //密码
ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
}
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
// 配置不会被拦截的链接 顺序判断,因为前端模板采用了thymeleaf,这里不能直接使用 ("/static/**", "anon")来配置匿名访问,必须配置到每个静态目录
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/html/**", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver
createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException","/user/403");
r.setExceptionMappings(mappings); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("exception"); // Default is "exception"
//r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
}
}
controller
@Controller
public class HomeController {
@Resource
private LoginService loginService;
@RequestMapping({"/","/index"})
public String index(){
return"/index";
}
@RequestMapping("/403")
public String unauthorizedRole(){
System.out.println("------没有权限-------");
return "/user/403";
}
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String toLogin(Map<String, Object> map,HttpServletRequest request)
{
loginService.logout();
return "/user/login";
}
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(Map<String, Object> map,HttpServletRequest request) throws Exception{
System.out.println("login()");
String userName = request.getParameter("userName");
String password = request.getParameter("password");
LoginResult loginResult = loginService.login(userName,password);
if(loginResult.isLogin())
{
return "/index";
}
else {
map.put("msg",loginResult.getResult());
map.put("userName",userName);
return "/user/login";
}
}
@RequestMapping("/logout")
public String logOut(HttpSession session) {
loginService.logout();
return "/user/login";
}
}
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 用户查询.
* @return
*/
@RequestMapping("/userList")
@RequiresPermissions("user:view")//权限管理;
public String userInfo(){
return "userList";
}
/**
* 用户添加;
* @return
*/
@RequestMapping("/userAdd")
@RequiresPermissions("user:add")//权限管理;
public String userInfoAdd(){
return "userAdd";
}
/**
* 用户删除;
* @return
*/
@RequestMapping("/userDel")
@RequiresPermissions("user:del")//权限管理;
public String userDel(){
return "userDel";
}
}
html
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../static/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}"></link>
<title>用户登录</title>
<style>
.bgColor{
background-color:rgba(243,66,111,0.15)
}
.divBorder{
border: solid 1px rgba(12,24,255,0.15);
padding: 10px;
margin-top: 10px;
border-radius: 10px;
text-align: center;
vertical-align: middle;
}
.h4font{
margin-top: 0px;
font-family: 微软雅黑;
font-weight: 500;
}
.center {
padding: 20% 0;
}
</style>
</head>
<body>
<div class="container">
<div class="row center">
<div class="divBorder col-sm-offset-3 col-sm-6">
<h3 class="panel panel-heading h4font">
用户登录
</h3>
<h4 th:text="${msg}"></h4>
<form class="form-horizontal" th:action="@{/login}" method="post">
<div class="input-group input-group-lg">
<span class="input-group-addon"><i class="glyphicon glyphicon-user" aria-hidden="true"></i></span>
<input type="text" class="form-control" id="userName" name="userName" placeholder="请输入用户名称" th:value="${userName}"/>
</div>
<br>
<div class="input-group input-group-lg">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<input type="password" class="form-control" id="password" name="password" placeholder="请输入密码"/>
</div>
<br>
<input type="submit" class="btn btn-lg btn-block btn-info" value="登 录">
</form>
</div>
</div>
</div>
</body>
</html>
其他的html页面自己随便生成就可以。