`
lee1177
  • 浏览: 117819 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

shiro与spring配合

阅读更多

之前一直使用spring security来做安全管理,感觉配置稍微有点复杂,于是尝试了下shiro,感觉的确简单不少。记录下配置和实现过程。

因为还是spring的底子,所以用的shiro-spring,首先用maven把相关包弄下来

dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring</artifactId>
	<version>${shiro.version}</version>
</dependency>

 我用的版本是1.2.2

然后开始增加shiro的配置文件,xml里增加了shiro的配置引入,同时增加相应的filter

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    	classpath*:/application-root.xml,
	classpath*:/application-shiro.xml
	</param-value>
  </context-param>
<!-- shiro security filter -->
	<filter>
	    <!-- 这里的filter-name要和spring的applicationContext-shiro.xml里的
	            org.apache.shiro.spring.web.ShiroFilterFactoryBean的bean name相同 -->
	    <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>
	</filter>
	
	<filter-mapping>
	    <filter-name>shiroFilter</filter-name>
	    <url-pattern>/*</url-pattern>
	</filter-mapping>

 下面是shiro的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"
	default-lazy-init="true">

	<description>Shiro安全配置</description>

	<bean id="chainDefinitionSectionMetaSource" class="xxx.xxx.ChainDefinitionSectionMetaSource">
	    <property name="filterChainDefinitions">
	        <value>
	            /login = authc
	            /logout = logout
	            /admin/** = roles[admin]
	            /static/** = anon
	            /eventSurvey/** = anon
	            /notice/** = anon
	            /** = authc
	        </value>
	    </property>
	</bean>
	<!-- Shiro's main business-tier object for web-enabled applications -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="shiroDbRealm" />
	</bean>

	<!-- 項目自定义的Realm, 所有accountService依赖的dao都需要用depends-on声明 -->
	<bean id="shiroDbRealm" class="xxx.xxx.ShiroDbRealm">
		<property name="credentialsMatcher" ref="flameCredentialsMatcher" />
	</bean>
	<bean id="flameCredentialsMatcher" class="xxx.xxx.FlameCredentialsMatcher">
	</bean>
	<!-- Shiro Filter -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<property name="loginUrl" value="/login" />
		<property name="successUrl" value="/" />
		<property name="unauthorizedUrl" value="/authError" />
		<property name="filters">
	        <map>
	            <entry key="authc">
	                <bean class="xxx.xxx.MyFormAuthenticationFilter"></bean>
	            </entry>
	             <entry key="roles">
	                <bean class="org.apache.shiro.web.filter.authz.RolesAuthorizationFilter"></bean>
	            </entry>
	        </map>
	    </property>
		<property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource" />
	</bean>
	
	<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
</beans>

 这里需要说明几个配置

1)shiro本身的过滤配置已经很好了,但我们要求可从数据库加载过滤配置,所以增加了chainDefinitionSectionMetaSource的实现,用来同时从配置文件和数据库加载过滤信息,实现如下

public class ChainDefinitionSectionMetaSource implements FactoryBean<Ini.Section>{
	@Autowired
    private ResourceDao resourceDao;

    private String filterChainDefinitions;

    public Section getObject() throws BeansException {
        //获取所有Resource
        List<Resource> list = resourceDao.findAll();

        Ini ini = new Ini();
        //加载默认的url
        ini.load(filterChainDefinitions);
        Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        //循环Resource的url,逐个添加到section中。section就是filterChainDefinitionMap,
        //里面的键就是链接URL,值就是存在什么条件才能访问该链接
        for (Resource resource : list) {
            //如果不为空值添加到section中
            if(StringUtils.hasText(resource.getUrl()) && StringUtils.hasText(resource.getPerms())) {
                section.put(resource.getUrl(),  resource.getPerms());
            }

        }

        return section;
    }

    /**
     * 通过filterChainDefinitions对默认的url过滤定义
     * 
     * @param filterChainDefinitions 默认的url过滤定义
     */
    public void setFilterChainDefinitions(String filterChainDefinitions) {
        this.filterChainDefinitions = filterChainDefinitions;
    }

    public Class<?> getObjectType() {
        return this.getClass();
    }

    public boolean isSingleton() {
        return false;
    }
}

 这里的Resource是从数据库得到的数据,其他跟xml配置差不多,就是key-value一样的。

2)shiroDbRealm,负责根据自己的业务抓取用户信息,主要是为了安全认证用

public class ShiroDbRealm extends AuthorizingRealm{
	@Autowired
	private UserService userService;

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		UserInfo user = (UserInfo) principals.getPrimaryPrincipal();
		List<String> userAuths = user.getAuthList();
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		info.addRoles(userAuths);
		return info;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authcToken) throws AuthenticationException {
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		UserInfo user = userService.findUserById(token.getUsername());
		if (user != null) {
			String authPassword = user.getPassword();
			return new SimpleAuthenticationInfo(user,
					authPassword,getName());
		} else {
			return null;
		}
	}

}

 3)credentialsMatcher,这里是处理密码匹配用的,通常密码都是加密的,用户前台输入的密码需要自己加密算法来匹配

/**
 * 处理密码加密
 * @author lee
 *
 */
public class FlameCredentialsMatcher extends SimpleCredentialsMatcher{
	@Override  
    public boolean doCredentialsMatch(AuthenticationToken token,  
            AuthenticationInfo info) {
		String userId = token.getPrincipal().toString();
		char[] ps = (char[]) token.getCredentials();
		StringBuffer sb = new StringBuffer();
		for(char p : ps){
			sb.append(p);
		}
		String tokenMd5Pw = Encryption.encrypt(userId,sb.toString());
        return equals(tokenMd5Pw, info.getCredentials());
    }  
}

 上面的Encryption.encrypt(userId,sb.toString())是一个加密算法处理,这个可以使用shiro的加密API,也可以自己实现。info是shiroDbRealm取回的用户信息,token是前台传过来的信息。

4)对于shiro的过滤链我使用了两个简单的,一个是处理登录和权限校验的authc,一个是处理角色校验的roles,这里需要说下的是如果在filter中不增加roles,那么配置/admin/** = roles[admin]这种就不会生效。对于authc因为要做是否是ajax的验证,所以做了自己的实现封装

public class MyFormAuthenticationFilter extends FormAuthenticationFilter{
	private static final Logger log = LoggerFactory.getLogger(MyFormAuthenticationFilter.class);

	/*
	 *  主要是针对登入成功的处理方法。对于请求头是AJAX的之间返回JSON字符串。
	 */
	@Override
	protected boolean onLoginSuccess(AuthenticationToken token,
	        Subject subject, ServletRequest request, ServletResponse response)
	        throws Exception {

	    if (!isAjax(request)) {// 不是ajax请求
	        issueSuccessRedirect(request, response);
	    } else {
	    	response.setCharacterEncoding("UTF-8");
	    	response.setContentType("text/plain;charset=utf-8");
	        PrintWriter out = response.getWriter();
	        out.println("{\"success\":true,\"message\":\"登入成功\"}");
	        out.flush();
	        out.close();
	    }
	    return false;
	}

	/**
	 * 主要是处理登入失败的方法
	 */
	@Override
	protected boolean onLoginFailure(AuthenticationToken token,
	        AuthenticationException e, ServletRequest request,
	        ServletResponse response) {
	    if (!isAjax(request)) {// 不是ajax请求
	        setFailureAttribute(request, e);
	        return true;
	    }
	    try {
	        response.setCharacterEncoding("UTF-8");
	        response.setContentType("text/plain;charset=utf-8");
	        PrintWriter out = response.getWriter();
	        String message = e.getClass().getSimpleName();
	        if ("IncorrectCredentialsException".equals(message)) {
	        	out.println("{\"success\":false,\"message\":\"密码错误\"}");
	        } else if ("UnknownAccountException".equals(message)) {
	        	out.println("{\"success\":false,\"message\":\"账号不存在\"}");
	        } else if ("LockedAccountException".equals(message)) {
	        	out.println("{\"success\":false,\"message\":\"账号被锁定\"}");
	        } else {
	        	out.println("{\"success\":false,message:\"未知错误\"}");
	        }
	        out.flush();
	        out.close();
	    } catch (IOException e1) {
	        // TODO Auto-generated catch block
	        e1.printStackTrace();
	    }
	    return false;
	}

	/**
	 * 所有请求都会经过的方法。
	 */
	@Override
	protected boolean onAccessDenied(ServletRequest request,
	        ServletResponse response) throws Exception {
		log.info("权限验证");
		if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
                return executeLogin(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                //allow them to see the login page ;)
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }
            if (!isAjax(request)) {// 不是ajax请求
	            saveRequestAndRedirectToLogin(request, response);
	        } else {
	            response.setCharacterEncoding("UTF-8");
	            response.setContentType("text/plain;charset=utf-8");
	            PrintWriter out = response.getWriter();
	            out.println("{\"success\":false,\"message\":\"login\"}");
	            out.flush();
	            out.close();
	        }
            return false;
        }
	}
	private boolean isAjax(ServletRequest request){
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		return "XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With"));
	}
}

 

都配置完成,shiro就跑起来了

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics