Shiro与spirng整合 WEB项目实现(jsp表单同步提交账户密码验证)
1.web.xml配置
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>targetBeanName</param-name>
//这里的名字为配置spring容器中ShiroFilterFactoryBean的id名 必须相同
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
DelegatingFilterProxy 是spring框架里,配上为代理实现webSecurityManagerFilter
注意:targetBeanName设置的shiroFilter需要和spring容器中ShiroFilterFactoryBean的id名相同
2.applicationContext-shiro.xml --spring配置
总体层次(RemenberMe功能实现还有点小问题~~)-----后面分模块解析
<!-- 这里的代理工厂与web.xml中的delegatingProxyFilter想对应,把shiro的filterbean纳入spring的filter管理 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="loginUrl" value="/login.action"/>
<property name="successUrl" value="/index.action"/>
<property name="unauthorizedUrl" value="/refuse.jsp"/>
<!-- 核心运作的securityManager -->
<property name="securityManager" ref="securityManager"/>
<!-- 各种filter过滤器的配置 -->
<property name="filterChainDefinitions">
<value>
<!-- -->
<!-- 公开资源释放匿名访问过滤器anon -->
/js/** = anon
/locale/** = anon
/plugins/** = anon
/themes/** = anon
<!-- 对验证码页面放行 -->
/validatecode.jsp = anon
<!-- 退出过滤器logout -->
/logout.action= logout
<!-- 配置授权 **这玩意要放在 /**=authc前面,不然就值套用认证即可使用的资源
/user/query.action = perms["user:query"] -->
<!-- 需要通过认证过滤器 才能访问的资源-->
/** = authc
</value>
</property>
<property name="filters">
<map>
<entry key="authc" value-ref="customFormAuthenticationFilter"/>
</map>
</property>
</bean>
<!-- 配个自定义FormAuthenticationFilter -->
<bean name="customFormAuthenticationFilter" class="com.sp.web.filter.CustomFormAuthenticationFilter">
<!-- 如果没配自定义认证过滤器,其用户名默认值即username,密码默认值即password,报错后存入request作用域
的Excoption Name默认值shiroLoginFailure -->
<property name="usernameParam" value="username" />
<property name="passwordParam" value="password" />
<property name="failureKeyAttribute" value="shiroLoginFailure" />
</bean>
<!-- 配入securityManager支持filter工厂的工作 -->
<bean name="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 需要一个自定义realm -->
<property name="realm" ref="customRealm"/>
<!-- 配置一个缓存,可以有效提高效率,当点击需要授权的链接是只需第一次差数据库,其他都会由本地缓存进行授权确认 -->
<property name="cacheManager" ref="ehCacheManager"/>
<!-- 配置记住我,即是网站常有的记住用户 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- 配置一个缓存bean -->
<bean name="ehCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro/shiro-ehcache.xml"/>
</bean>
<!-- 配入自定义的realm -->
<bean name="customRealm" class="com.sp.realm.CustomRealm">
<!-- 配置凭证匹配器,MD5加盐算法 -->
<property name="credentialsMatcher" ref="hashedCredentialsMatcher"/>
</bean>
<!-- 凭证匹配器 -->
<bean name="hashedCredentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="1"/>
</bean>
<!-- 记住我功能实现的manager -->
<bean name="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="simpleCookie"/>
</bean>
<!-- 配置记住我的需要的cookie -->
<bean name="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- 这个是配置cookie存活时间,记住7天 -->
<property name="maxAge" value="604800"/>
<!-- 添加构造参数的值为登录页面中的记住我复选框的name属性值 -->
<constructor-arg value="rememberMe"/>
</bean>
模块(1)认证授权实现部分
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 如果没有认证过拦截后进入的url -->
<property name="loginUrl" value="/login.action"/>
<!-- 认证通过后进入的url -->
<property name="successUrl" value="/index.action"/>
<!-- 没有权限被拦截后进入的url -->
<property name="unauthorizedUrl" value="/refuse.jsp"/>
<!-- 核心运作的securityManager -->
<property name="securityManager" ref="securityManager"/>
<!-- 各种filter过滤器的配置 -->
<property name="filterChainDefinitions">
<value>
<!-- -->
<!-- 公开资源释放匿名访问过滤器anon -->
/js/** = anon
/locale/** = anon
/plugins/** = anon
/themes/** = anon
<!-- 对验证码页面放行 -->
/validatecode.jsp = anon
<!-- 退出过滤器logout -->
/logout.action= logout
<!-- 配置授权 **这玩意要放在 /**=authc前面,不然就值套用认证即可使用的资源
/user/query.action = perms["user:query"] -->
<!-- 需要通过认证过滤器 才能访问的资源-->
/** = authc
</value>
</property>
</bean>
<!-- 配入securityManager支持filter工厂的工作 -->
<bean name="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 需要一个自定义realm -->
<property name="realm" ref="customRealm"/>
</bean>
<!-- 配入自定义的realm -->
<bean name="customRealm" class="com.sp.realm.CustomRealm">
<!-- 配置凭证匹配器,MD5加盐算法 -->
<property name="credentialsMatcher" ref="hashedCredentialsMatcher"/>
</bean>
<!-- 凭证匹配器 -->
<bean name="hashedCredentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="1"/>
</bean>
这个是shirofilter的代理工厂,内含securityManager进行认证和授权的处理.
filterChainDefinitions这个属性则是定义各种类型shiroflter所管理的url,缩写详细见附录
securityManager必须要有个自定义Realm,只有自定义的Realm才能实现与数据库进行交互
customRealm自定义的realm继承AuthorizingRealm,重写认证和授权方法.
public class CustomRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
try {
//授权操作,从主体中拿到用户进行授权,需要查找数据库总该用户所有的权限
SysUser user = (SysUser) principals.getPrimaryPrincipal();
List<SysPermission> Perssions = sysUserService.findPerssionsByUserId(user.getUsercode());
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> stringPermissions = new HashSet<>();
for (SysPermission permisson : Perssions) {
System.out.println(permisson.getPercode());
stringPermissions.add(permisson.getPercode());
}
authorizationInfo.setStringPermissions(stringPermissions );
return authorizationInfo;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
try {
//通过token拿到前端输入的用户名再从数据库中查找
String userCode = (String) token.getPrincipal();
SysUser user = sysUserService.findUserByUsercode(userCode);
//找不到用户则返回null,找到则需要赋予menu及找到密码 后续匹配密码;
//这里的menu为自定义的url,用户拥有相应的url才会在页面显示相应的a标签(按需来写)
if (user==null) return null;
List<SysPermission> menus = sysUserService.getMenusByUsercode(userCode);
user.setMenus(menus);
String password = user.getPassword();
String salt = user.getSalt();
//把找到的密码和盐返回到上层进行匹配
//这里注意返回的principal直接使用数据库查找到的用户对象
return new SimpleAuthenticationInfo(user, password, ByteSource.Util.bytes(salt),"myRealm");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
----配置注解授权限制访问----在springmvc中配置(如果直接用perms过滤器则不需要)
<!-- requiredPermisson 注解配置1:需求代理确认? -->
<aop:config proxy-target-class="true"></aop:config>
<!-- requiredPermisson 注解配置2:类似拦截器要配一个? -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"></property>
</bean>
<!-- requiredPermisson 注解配置3:因为这里会套用默认的报错方式,所以需要自定义视图 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">anno-refuse</prop>
</props>
</property>
</bean>
@RequestMapping("/user/query")
//下面的注解代表如果要访问/user/query.action这个资源,则必须当前用户在数据库中有user:queryList权限才行。
@RequiresPermissions("user:queryList")
public String query() {
return "layout/user/query";
}
模块(2)认证filter重写实现验证码登录
在ShiroFilterFactoryBean添加属性
<property name="filters">
<map>
<entry key="authc" value-ref="customFormAuthenticationFilter"/>
</map>
</property>
<!-- 配个自定义FormAuthenticationFilter -->
<bean name="customFormAuthenticationFilter" class="com.sp.web.filter.CustomFormAuthenticationFilter">
<property name="usernameParam" value="username" />
<property name="passwordParam" value="password" />
<property name="failureKeyAttribute" value="shiroLoginFailure" />
</bean>
自定义的过滤器继承AuthenticationFilter
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//转型
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse rep = (HttpServletResponse) response;
//这里是拿到jsp丢入到session里的正确验证码
String vaCodeRight = (String) req.getSession().getAttribute("validateCode");
//拿到用户输入的验证码
String vaCodeIn = req.getParameter("validcode");
//!!!!这里有个BUG 如果不输入验证码,只有账号密码也能登录,如果设定验证码为空进入下面方法也会导致直接匿名登录!!
//所以在页面的表单提交需要通过不能为空验证,才能提交表单
if (!StringUtils.isEmpty(vaCodeIn)&&!vaCodeIn.equals(vaCodeRight)){
req.setAttribute("shiroLoginFailure", "validcodeError");
return true;
}
return super.isAccessAllowed(request, response, mappedValue);
}
}
验证码生成页面见附录
模块(3)缓存管理,提高性能,防止每次访问权限连接都要访问数据库
在securityManager中添加属性,并引入ehCacheManager的一个实现Bean
<!-- 配置一个缓存,可以有效提高效率,当点击需要授权的链接是只需第一次差数据库,其他都会由本地缓存进行授权确认 -->
<property name="cacheManager" ref="ehCacheManager"/>
<!-- 配置一个缓存bean -->
<bean name="ehCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro/shiro-ehcache.xml"/>
</bean>
缓存的配置xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--diskStore:缓存数据持久化的目录 地址 -->
<diskStore path="e:\develop\ehcache" />
<!--
“diskStore path=”java.io.tmpdir”:磁盘的存储目录;
“name=”xxx””:对要进行缓存的项进行一个标注;
“maxElementsInMemory=”2000”:可以缓存的最大的对象个数;
“eternal=”false”:是否允许自动失效(如果某一个对象长时间不使用);
“timeToIdleSeconds=”1800”:最小的失效时间,1800秒;
“timeToLiveSeconds=”0”:最大的保存时间,单位是秒;
“overflowToDisk=”false”:如果容量过多,可以将其保存在磁盘
-->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
模块(4)RememberMe功能
在SecurityManager添加属性
<!-- 配置记住我,即是网站常有的记住用户 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
<!-- 记住我功能实现的manager -->
<bean name="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="simpleCookie"/>
</bean>
<!-- 配置记住我的需要的cookie -->
<bean name="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- 这个是配置cookie存活时间,记住7天 -->
<property name="maxAge" value="604800"/>
<!-- 添加构造参数的值为登录页面中的记住我复选框的name属性值 -->
<constructor-arg value="rememberMe"/>
</bean>
需要配置user过滤器来实现
<!-- 配置记住我及认证通过的用户可以访问的资源 -->
/index.action = user
/user/** = user
/menu/** = user
/student/** = user
3.controller
@Controller
public class UserController {
@RequestMapping("/login")
public String login(HttpServletRequest request,HttpServletResponse response) throws Exception{
//当请求到来时,由FormAuthenticationFilter进行认证,由realm根据当前用户名查询用户信息,如果返回null,则
//FormAuthenticationFilter对象将错误信息封装到request中,并以shiroLoginFailure为参数存放(自定义authc过滤
//器之后改为errorMsg)
String exceptionName = (String) request.getAttribute("shiroLoginFailure");
System.out.println("===========>" + exceptionName);
if(exceptionName != null){
if(IncorrectCredentialsException.class.getName().equals(exceptionName)){
throw new MyException("用户名/密码有误!");
}else if (UnknownAccountException.class.getName().equals(exceptionName)){
throw new MyException("账户异常!");
}else if(UnauthorizedException.class.getName().equals(exceptionName)){
throw new MyException("无权限访问异常!");
}else if("validcodeError".equals(exceptionName)){
throw new MyException("验证码错误!");
}else{
throw new Exception();
}
}
//此方法只有在FormAuthenticationFilter认证失败时工作,认证成功返回页面就是上一次的页面
return "login";
}
附录
过滤器简称对应的java 类
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
anon:例子/admins/=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/=authc 表示需要认证(登录)才能使用,FormAuthenticationFilter 是
表单认证,没有参数
roles:例子/admins/user/=roles[admin],参数可以写多个,多个时必须加上引号,并且参数
之间用逗号分割,当有多个参数时,例如admins/user/=roles[“admin,guest”],每个参数通过
才算通过,相当于hasAllRoles()方法。
perms:例子/admins/user/=perms[user:add:*],参数可以写多个,多个时必须加上引号,并
且参数之间用逗号分割,例如/admins/user/=perms[“user:add:,user:modify:”],当有多个
参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
user:例如/admins/user/**=user 没有参数表示必须存在用户, 身份认证通过或通过记住我认
证通过的可以访问,当登入操作时不做检查
注:
anon,authcBasic,auchc,user 是认证过滤器,
perms,roles,ssl,rest,port 是授权过滤器
验证码生成页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.Random"%>
<%@ page import="java.io.OutputStream"%>
<%@ page import="java.awt.Color"%>
<%@ page import="java.awt.Font"%>
<%@ page import="java.awt.Graphics"%>
<%@ page import="java.awt.image.BufferedImage"%>
<%@ page import="javax.imageio.ImageIO"%>
<%
int width = 60;
int height = 32;
//create the image
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
// set the background color
g.setColor(new Color(0xDCDCDC));
g.fillRect(0, 0, width, height);
// draw the border
g.setColor(Color.black);
g.drawRect(0, 0, width - 1, height - 1);
// create a random instance to generate the codes
Random rdm = new Random();
String hash1 = Integer.toHexString(rdm.nextInt());
System.out.print(hash1);
// make some confusion
for (int i = 0; i < 50; i++) {
int x = rdm.nextInt(width);
int y = rdm.nextInt(height);
g.drawOval(x, y, 0, 0);
}
// generate a random code
String capstr = hash1.substring(0, 4);
//将生成的验证码存入session
session.setAttribute("validateCode", capstr);
g.setColor(new Color(0, 100, 0));
g.setFont(new Font("Candara", Font.BOLD, 24));
g.drawString(capstr, 8, 24);
g.dispose();
//输出图片
response.setContentType("image/jpeg");
out.clear();
out = pageContext.pushBody();
OutputStream strm = response.getOutputStream();
ImageIO.write(image, "jpeg", strm);
strm.close();
%>