一、 准备
1.数据库
创建表db_blogger:
1 DROP TABLE IF EXISTS `t_blogger`; 2 3 CREATE TABLE `t_blogger` ( 4 5 `id` INT(11) NOT NULL AUTO_INCREMENT, 6 7 `userName` VARCHAR(50) DEFAULT NULL, 8 9 `password` VARCHAR(100) DEFAULT NULL, 10 11 `profile` TEXT, 12 13 `nickName` VARCHAR(50) DEFAULT NULL, 14 15 `sign` VARCHAR(100) DEFAULT NULL, 16 17 `imageName` VARCHAR(100) DEFAULT NULL, 18 19 `salt` VARCHAR(100) DEFAULT NULL, 20 21 PRIMARY KEY (`id`) 22 23 ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 24 25 INSERT INTO `t_blogger`(`id`,`userName`,`password`,`profile`,`nickName`,`sign`,`imageName`,`salt`) VALUES (1,‘neya‘,‘7f50069012f25a74bb2783a2d7100f0e‘,NULL,NULL,NULL,NULL,‘Neya‘);
2.添加shiro支持
1 <!-- 添加shiro支持 --> 2 3 <dependency> 4 5 <groupId>org.apache.shiro</groupId> 6 7 <artifactId>shiro-core</artifactId> 8 9 <version>1.2.4</version> 10 11 </dependency> 12 13 14 15 <dependency> 16 17 <groupId>org.apache.shiro</groupId> 18 19 <artifactId>shiro-web</artifactId> 20 21 <version>1.2.4</version> 22 23 </dependency> 24 25 26 27 <dependency> 28 29 <groupId>org.apache.shiro</groupId> 30 31 <artifactId>shiro-spring</artifactId> 32 33 <version>1.2.4</version> 34 35 </dependency>
二、 登录界面及登录控制
1. 登录界面
在webapp文件夹下创建login.jsp,以上所需文件及图片可由以下链接下载:链接:https://pan.baidu.com/s/1bqCGJld 密码:701g
登录界面如下:
2. 创建Realm并配置shiro
Shiro是一个Java平台的开源权限框架,用于认证和访问授权。具体来说,满足对如下元素的支持:
1)用户,角色,权限(仅仅是操作权限,数据权限必须与业务需求紧密结合),资源(url)。
2)用户分配角色,角色定义权限。
3)访问授权时支持角色或者权限,并且支持多级的权限定义。
关于shiro更详细的信息,推荐阅读博客:http://jinnianshilongnian.iteye.com/blog/2018398,暂时可以只阅读前几章,足够本项目使用。
下面简单说一下本项目中所用到的shiro执行流程:前端从login.jsp获取到用户提交的用户名及密码的表单数据提交到后台的controller处理,在controller相应的登录功能模块中将用户名和密码封装到一个token中(UsernamePasswordToken对象)。在shiro中,应用代码直接交互的对象是Subject,也就是说shiro的对外API核心就是Subject,其代表了当前“用户”,这个用户不一定是具体的人,也可以是与当前应用交互的任何东西。Subject会绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager。在controller中,通过一个Subject实例通过login方法将之前创建的token提交,后续会运行到自定义的MyRealm类,在此类中由方法doGetAuthenticationInfo实现具体的验证逻辑,首先需要根据提交的用户名到数据库中进行查询,得到数据库中相应的密码及盐,然后将用户名、密码、盐封装到一个info(SimpleAuthenticationInfo)中,作为方法的返回值返回,接下来的验证由shiro进行处理,主要是对比提交的token和info中的信息,将token中的密码取出与info中取出的盐使用MD5加密(加密算法及次数在shiro配置文件中配置),得到加密后的字符串与info中的密码字符串进行匹配,相同则验证成功,否则抛出异常。
创建包com.neya.shiro,并在其中创建MyRealm类:
1 public class MyRealm extends AuthorizingRealm{ 2 3 4 5 @Override 6 7 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 8 9 10 11 return null; 12 13 } 14 15 16 17 @Override 18 19 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 20 21 // TODO Auto-generated method stub 22 23 24 25 return null; 26 27 } 28 29 30 31 public String getName(){ 32 33 return "myRealm"; 34 35 } 36 37 38 39 }
在resource中创建shiro 配置文件:applicationContext-shiro:
1 <?xml version="1.0" encoding="UTF-8"?> 2 3 <beans xmlns="http://www.springframework.org/schema/beans" 4 5 xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" 6 7 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 8 9 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 10 11 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 12 13 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd 14 15 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 16 17 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd 18 19 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"> 20 21 22 23 24 25 26 27 <!--securityManage--> 28 29 <!-- 安全管理器 --> 30 31 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 32 33 <property name="realm" ref="customRealm" /> 34 35 </bean> 36 37 38 39 40 41 <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> 42 43 44 45 <bean id="customRealm" class="com.ssm.shiro.MyRealm"> 46 47 48 49 <property name="credentialsMatcher"> 50 51 <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> 52 53 <!-- 加密方式 --> 54 55 <property name="hashAlgorithmName" value="MD5" /> 56 57 <!-- 加密次数 --> 58 59 <property name="hashIterations" value="1"/> 60 61 </bean> 62 63 </property> 64 65 </bean> 66 67 68 69 <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> 70 71 <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> 72 73 <property name="securityManager" ref="securityManager"/> 74 75 </bean> 76 77 78 79 80 81 <!--web.xml中shiro的filter对应的bean--> 82 83 <!-- Shiro 的Web过滤器 --> 84 85 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 86 87 <property name="securityManager" ref="securityManager" /> 88 89 <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 --> 90 91 <property name="loginUrl" value="/blogger/login.do" /> 92 93 <!-- 认证成功统一跳转到first.do,建议不配置,不配置的话shiro认证成功会自动到上一个请求路径 --> 94 95 <property name="filterChainDefinitions"> 96 97 <value> 98 99 100 101 /login=anon 102 103 /admin/**=authc 104 105 106 107 <!-- -/**=authc 表示所有的url都必须认证通过才可以访问- --> 108 109 110 111 <!--/**=anon 表示所有的url都可以匿名访问,anon是shiro中一个过滤器的简写,关于shiro中的过滤器介绍见--> 112 113 114 115 </value> 116 117 </property> 118 119 </bean> 120 121 122 123 </beans>
3. 登录控制
使用上一篇中的逆向工程文件generatorConfig.xml将表t_blogger生成相应的实体类及mapper文件,创建相应的Service及实现类:
BloggerService.java:
1 package com.neya.service; 2 3 import com.neya.domain.Blogger; 4 5 public interface BloggerService { 6 7 public Blogger getBloggerByName(String username); 8 9 }
BloggerServiceImpl.java:
1 package com.neya.service.impl; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 5 import org.springframework.stereotype.Service; 6 7 import com.neya.domain.Blogger; 8 9 import com.neya.mapper.BloggerMapper; 10 11 import com.neya.service.BloggerService; 12 13 14 15 @Service 16 17 public class BloggerServiceImp implements BloggerService { 18 19 20 21 @Autowired 22 23 private BloggerMapper bloggerMapper; 24 25 26 27 @Override 28 29 public Blogger getBloggerByName(String username) { 30 31 // TODO Auto-generated method stub 32 33 Blogger blogger=bloggerMapper.getByUserName(username); 34 35 return blogger; 36 37 } 38 39 }
由于逆向工程自动创建的mapper中没有getByUserName方法,所以需要在BloggerMapper.java中添加此方法并在BloggerMapper.xml中实现:
1 <select id="getByUserName" parameterType="String" resultMap="ResultMapWithBLOBs"> 2 3 select * from t_blogger where userName=#{userName} 4 5 </select>
在MyRealm的doGetAuthenticationInfo方法中添加如下代码:
1 String username=(String) token.getPrincipal(); 2 3 Blogger blogger=bloggerService.getBloggerByName(username); 4 5 if(blogger!=null){//能查询到数据则将查询到的数据封装到info中与token比较 6 7 SecurityUtils.getSubject().getSession().setAttribute("currentUser", blogger); 8 9 ByteSource credentialsSalt = ByteSource.Util.bytes(blogger.getSalt()); 10 11 AuthenticationInfo info=new SimpleAuthenticationInfo(blogger.getUsername(), blogger.getPassword(),credentialsSalt,getName()); 12 13 return info; 14 15 }else{ 16 17 return null; 18 19 }
前面的login.jsp中,表单提交的地址为"${pageContext.request.contextPath}/blogger/login.do",所以在controller包中创建相应的BloggerController类并使用@RequestMapping注解映射:
package com.neya.controller; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.neya.domain.Blogger; import com.neya.service.BloggerService; @Controller @RequestMapping("/blogger") public class BloggerController { @Autowired private BloggerService bloggerService; @RequestMapping("/login") public String login(Blogger blogger,HttpServletRequest request){//参数使用spring的自动绑定,从blogger中寻找相应的字段与前端提交的表单的‘name‘匹配,相同则会对blogger的属性直接赋值,比如表单中提交了两项,name属性分别为username和password,则在blogger中,若发现有这两个字段,则直接将提交的数据对这两个字段赋值 Subject subject=SecurityUtils.getSubject(); UsernamePasswordToken token=new UsernamePasswordToken(blogger.getUsername(),blogger.getPassword());//根据前端提交的信息创建token try{ subject.login(token);//登录,这里会执行到MyRealm中的代码 return "redirect:/admin/main.jsp";//登录成功则跳转到相应的后台管理界面 }catch(Exception e){//验证失败会抛出异常,将失败信息写回前端 e.printStackTrace(); request.setAttribute("blogger", blogger); request.setAttribute("errorInfo", "用户名或密码错误"); return "login"; } } }
main.jsp及其所需要的文件在以下链接中:
链接:https://pan.baidu.com/s/1jJ9K8rO 密码:l65t
main.jsp放在webapp下的admin文件夹中
另外,在web.xml中还需要加入shiro过滤器
1 <!--在这里配置shiro的filter--> 2 3 <!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 --> 4 5 <filter> 6 7 <filter-name>shiroFilter</filter-name> 8 9 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 10 11 <!-- 设置true由servlet容器控制filter的生命周期 --> 12 13 <init-param> 14 15 <param-name>targetFilterLifecycle</param-name> 16 17 <param-value>true</param-value> 18 19 </init-param> 20 21 <!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean --> 22 23 <init-param> 24 25 <param-name>targetBeanName</param-name> 26 27 <param-value>shiroFilter</param-value> 28 29 </init-param> 30 31 </filter> 32 33 <filter-mapping> 34 35 <filter-name>shiroFilter</filter-name> 36 37 <url-pattern>/*</url-pattern> 38 39 </filter-mapping>
接下来启动tomcat,并在浏览器中输入http://localhost:8080/MyBlog/login.jsp,填入用户名neya及密码123456,点击登录即可跳转到相应后台管理界面