您的位置:首页 > 编程语言 > Java开发

shiro框架---关于用户登录和权限验证功能的实现步骤(三)

2018-02-18 22:49 3235 查看
接上一篇文章shiro框架—关于用户登录和权限验证功能的实现步骤(二)

shiro在springboot项目中的配置步骤

1、引入依赖

  首先shiro的应用,引入的依赖仅仅只有一个,即下边这个。

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>


2、shiro在springboot项目中的位置:

  以下是shiro在springboot项目中的位置:



  主要的文件有四个
ShiroConfig
RetryLimitHashedCredentialsMatcher
UserRealm
MShiroFilterFactoryBean
。在这里
`UserOAuthMatcher
  没有用到,其实它跟
RetryLimitHashedCredentialsMatcher
是一样的意思,都是实现的同一个shiro的接口,用哪一个都可以,我在下边的链接里就不放进
UserOAuthMatcher
了。

3、以上配置文件的主要功能:

(1) shiroConfig

  以下是
shiroConfig
文件的全部配置。其他的配置文件都是围绕这个文件展开工作的。


package microservice.fpzj.shiro;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.filter.DelegatingFilterProxy;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
filterRegistration.setEnabled(true);
filterRegistration.addUrlPatterns("/*"); //过滤规则,即所有的请求
filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
return filterRegistration;
}

/**
* 这个即是上边调用的shiroFilter过滤器,也就是shiro配置的过滤器
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(){
/**
*MShiroFilterFactoryBean指向自定义过滤器,自定义过滤器对js/css等忽
*略
**/
ShiroFilterFactoryBean bean = new MShiroFilterFactoryBean();
bean.setSecurityManager(securityManager());
bean.setLoginUrl("/login");
bean.setUnauthorizedUrl("/unauthor");
Map<String, Filter>filters = new LinkedHashMap<>();
//      filters.put("perms", urlPermissionsFilter());
filters.put("anon", new AnonymousFilter());
bean.setFilters(filters);
//shiro配置过滤规则少量的话可以用hashMap,数量多了要用LinkedHashMap,保证有序,原因未知
Map<String, String> chains = new LinkedHashMap<>();
chains.put("/login", "anon");
chains.put("/unauthor", "anon");
chains.put("/logout", "anon");
chains.put("/weblogin", "anon");
chains.put("/**", "authc");
bean.setFilterChainDefinitionMap(chains);
return bean;
}

/**
* @see org.apache.shiro.mgt.SecurityManager
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm());
//manager.setCacheManager(cacheManager());
manager.setSessionManager(defaultWebSessionManager());
return manager;
}

/**
* @see DefaultWebSessionManager
* @return
*/
@Bean(name="sessionManager")
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//sessionManager.setCacheManager(cacheManager());
sessionManager.setGlobalSessionTimeout(1800000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdCookie(getSessionIdCookie());
return sessionManager;
}
/**
* 给shiro的sessionId默认的JSSESSIONID名字改掉
* @return
*/
@Bean(name="sessionIdCookie")
public SimpleCookie getSessionIdCookie(){
SimpleCookie simpleCookie = new SimpleCookie("webcookie");
/**
* HttpOnly标志的引入是为了防止设置了该标志的cookie被JavaScript读取,
* 但事实证明设置了这种cookie在某些浏览器中却能被JavaScript覆盖,
* 可被攻击者利用来发动session fixation攻击
*/
simpleCookie.setHttpOnly(true);
/**
* 设置浏览器cookie过期时间,如果不设置默认为-1,表示关闭浏览器即过期
* cookie的单位为秒 比如60*60为1小时
*/
simpleCookie.setMaxAge(-1);
return simpleCookie;
}

/**
* @see UserRealm--->AuthorizingRealm
* @return
*/
@Bean
@DependsOn(value="lifecycleBeanPostProcessor")
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(credentialsMatcher());
//userRealm.setCacheManager(cacheManager());
return userRealm;
}
@Bean(name="credentialsMatcher")
public CredentialsMatcher credentialsMatcher() {
return new RetryLimitHashedCredentialsMatcher();
}

/*@Bean
public EhCacheManager cacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}*/

@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}


下边针对于
shiroConfig
文件中的配置,一一说明。


  首先该文件的第一个配置为
FilterRegistrationBean
。 springboot注入过滤器有多种方式,一种是最简单的@WebFilter注解,一种就是下边的这种,写一个FilterRegistrationBean,然后将自定义过滤器set进去,下边是通过
DelegatingFilterProxy
代理的方式,注入容器中名字为
shiroFilter
的过滤器,最后设置过滤器的规则。

@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
/**
*DelegatingFilterProxy做的事情是代理Filter的方法,从application
*context里获得bean,从下边可以理解到,它是将容器中名字为shiroFilter
*的过滤器加入到过滤器注册bean中
**/
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
filterRegistration.setEnabled(true);
filterRegistration.addUrlPatterns("/*"); //过滤规则,即所有的请求
filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
return filterRegistration;
}


  既然上边注入的是名字为
shiroFilter
的过滤器,那下边就是
shiroFilter
的配置

/**
* 这个即是上边调用的shiroFilter过滤器,也就是shiro配置的过滤器
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(){
/**
*MShiroFilterFactoryBean指向自定义过滤器,自定义过滤器对js/css等忽
*略
**/
ShiroFilterFactoryBean bean = new MShiroFilterFactoryBean();
bean.setSecurityManager(securityManager());
bean.setLoginUrl("/login");
bean.setUnauthorizedUrl("/unauthor");
Map<String, Filter>filters = new LinkedHashMap<>();
//      filters.put("perms", urlPermissionsFilter());
/**
* shiro自己的过滤器,anon,表示不拦截的路径,authc,表示拦截的路径
**/
filters.put("anon", new AnonymousFilter());
bean.setFilters(filters);
/*
*shiro配置过滤规则少量的话可以用hashMap,数量多了要用
*LinkedHashMap,保证有序,原因未知。
*,anon,表示不拦截的路径,authc,表示拦截的路径。匹配时,首先匹配
*anon的,然后最后匹配authc
**/
Map<String, String> chains = new LinkedHashMap<>();
chains.put("/login", "anon");
chains.put("/unauthor", "anon");
chains.put("/logout", "anon");
chains.put("/weblogin", "anon");
chains.put("/**", "authc");
bean.setFilterChainDefinitionMap(chains);
return bean;
}


  以上的
shiroFilter
配置中,又引入了我们的第二个配置文件,名字为
MShiroFilterFactoryBean
,该类继承了
ShiroFilterFactoryBean
类,通过名字,应该大体能知道,它是shiro的过滤器工厂类,而我们的
MShiroFilterFactoryBean
类就是一个自定义的shiro过滤器,为什么要自己写一个过滤器呢?

  在当前的shiro框架中,无法拦截那种
.js
.css
.html
.jsp
等等带有
.
的请求路径,对于前三种这种静态资源,我们可以不纳入shiro拦截,即可以在不登录的情况下访问成功,但是我们现在有这样的需求,即对
.jsp
也要拦截起来,那就需要自定义shiro过滤器了,即现在
MShiroFilterFactoryBean
类存在的意义。关于该类的解释,在后边再说,我们继续
shiroConfig
文件的介绍。

  在上边的配置中,其实就是自定义了一个shiro过滤器,然后对其进行了一些操作,其中
bean.setLoginUrl("/login")
是在项目启动后,如果没有登录的情况下,会被shiro强制请求的路径,即为
/login


  另外,
bean.setSecurityManager(securityManager());
这句的配置,即引入设置shiro的控制中心,即
securityManager
,安全管理器;即所有与安全有关的操作都会与
securityManager
交互;且它管理着所有
Subject
;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;关于
subject
,你就理解为是每一个访问系统的用户对象即可,所有的访问用户的情况都是一种
subject
的体现,它们又统一被
securityManager
管理,这个在第一篇里已经说过。

  通过上边的
shiroFilter
的配置之后,然后再看
securityManager


/**
* @see org.apache.shiro.mgt.SecurityManager
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm());
//manager.setCacheManager(cacheManager());
manager.setSessionManager(defaultWebSessionManager());
return manager;
}


  以上又引入了我们的第三个配置文件,即
UserRealm
文件,改文件又引出了我们的一个概念,
Realm
:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

  在这里的
UserRealm
类继承于
AuthorizingRealm
,该类的作用其实有用户密码验证、权限授权等。这个也会在后边贴出来,先继续讲
shiroConfig
文件。

  通过上边的
manager.setSessionManager(defaultWebSessionManager());
然后引入下边的配置

/**
* @see DefaultWebSessionManager
* @return
*/
@Bean(name="sessionManager")
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//sessionManager.setCacheManager(cacheManager());
sessionManager.setGlobalSessionTimeout(1800000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdCookie(getSessionIdCookie());
return sessionManager;
}


  上边的配置,主要是对
session
的配置,比如,超时时间的设置等,基本上都是跟
session
相关的配置。另外,上边还有
getSessionIdCookie()
方法的引用,众所周知,浏览器与后台系统交互的方式,是以后台存储
session
,然后将该
session
对应key,以字符串的形式返给浏览器,并在浏览器中以
cookie
的形式记录起来,方便后续的访问,如果浏览器丢失了这个
cookie
,那就会失去与后台系统的联系,必须重新登录,才能重新再生成这个
cookie
。而
getSessionIdCookie()
方法,即是对cookie在浏览器那里的名字的定义,如下:

/**
* 给shiro的sessionId默认的JSSESSIONID名字改掉
* @return
*/
@Bean(name="sessionIdCookie")
public SimpleCookie getSessionIdCookie(){
SimpleCookie simpleCookie = new SimpleCookie("webcookie");
/**
* HttpOnly标志的引入是为了防止设置了该标志的cookie被JavaScript读取,
* 但事实证明设置了这种cookie在某些浏览器中却能被JavaScript覆盖,
* 可被攻击者利用来发动session fixation攻击
*/
simpleCookie.setHttpOnly(true);
/**
* 设置浏览器cookie过期时间,如果不设置默认为-1,表示关闭浏览器即过期
* cookie的单位为秒 比如60*60为1小时
*/
simpleCookie.setMaxAge(-1);
return simpleCookie;
}


  以上配置起的名字为
webcookie
,这个
webcookie
就是浏览器那边存储后台传入过来的
cookie
的key。整个的大体流程,我理解的如下:



  以后再请求,只要浏览器没有清除
cookie
,上边关于
session
的超时时间没有超时,就可以正常访问系统。之所以,这里要给
sessionid
起一个名字
webcookie
这是防止浏览器访问多个系统的时候,恰巧碰上两个系统在浏览器那边存储
sessionid
对应的key正好相同,即
session污染
,造成访问系统出现问题。如果不设置,shiro默认的
sessionid
在前端浏览器的名字为
cookie


  关于
session污染
的异常,我遇到的是下边这个:

Found 'sid' cookie value [1a22b751-0542-4e74-a8e7-59942692f6ae]
22:13:37 DEBUG net.sf.ehcache.Cache - mx-master-SessionCache cache - Miss
22:13:37 DEBUG o.a.shiro.mgt.DefaultSecurityManager - Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous (session-less) Subject instance.
org.apache.shiro.session.UnknownSessionException: There is no session with id [1a22b751-0542-4e74-a8e7-59942692f6ae]


  如果出现
There is no session with id
的异常,不出意外的话,就是上边的配置有问题,需要有
cookie
的配置,相关的文章,你可以看这一篇一个项目两个web模块会导致shiro的session污染 ,可以得到解释。

  继续
shiroconfig
文件的配置,然后再后边就是如下配置:

@Bean
@DependsOn(value="lifecycleBeanPostProcessor")
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(credentialsMatcher());
//userRealm.setCacheManager(cacheManager());
return userRealm;
}
@Bean(name="credentialsMatcher")
public CredentialsMatcher credentialsMatcher() {
return new RetryLimitHashedCredentialsMatcher();
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}


  上边有
userRealm
类的注入,而在前边
securityManager
的配置的时候,它引入了
userRealm
,就是在这里对
userRealm
类注入的。

  另外,上边
userRealm
方法中设置了一个
credentialsMatcher()
,该方法对应的就是
new RetryLimitHashedCredentialsMatcher()
类,这里就引入了我们第四个配置文件
RetryLimitHashedCredentialsMatcher
类,该类,继承于
HashedCredentialsMatcher
类,而
HashedCredentialsMatcher
你如果往上找,其实就是实现了
CredentialsMatcher
接口,所以这里注入的时候,可以以自定义的
RetryLimitHashedCredentialsMatcher
类注入成
CredentialsMatcher
,该类的功能主要是将用户输入的密码与查询到的密码进行比较,也就是密码比较器。

  这个
shiroConfig
类写的有点多,我理解的也有点不足,有些片面,如果有不对的地方,请读者帮我指正,我及时改过来。

  另外提到的另外三个配置文件,先不写了,放到下一篇吧,今天写的有点多了。先贴上shiro的这四个配置文件的下载地址shiro的配置

下一篇文章shiro框架—关于用户登录和权限验证功能的实现步骤(四)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐