配色: 字号:
Spring Boot系列 安全框架Apache Shiro基本功能
2016-09-18 | 阅:  转:  |  分享 
  
SpringBoot系列安全框架ApacheShiro基本功能

ApacheShiro是Java的一个安全框架。目前,使用ApacheShiro的人越来越多,因为它相当简单,对比SpringSecurity,可能没有SpringSecurity做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。

详细基础知识,请参考跟我学Shiro的系列文章



这里只是给出SpringBoot集成Shiro的案例,SpringBoot就是为了简化传统Spring开发的复杂度,即去xml化,所以案例中也是没有xml配置,完全javaconfig方式配置。



集成Shiro核心内容:



ShiroFilterFactory,Shiro过滤器工程类,具体的实现类是:ShiroFilterFactoryBean,此实现类是依赖于SecurityManager安全管理器。主要配置Filter就好。

SecurityManager,Shiro的安全管理,主要是身份认证的管理,缓存管理,cookie管理,所以在实际开发中我们主要是和SecurityManager进行打交道的。

Realm,用于身份信息权限信息的验证。开发时集成AuthorizingRealm,重写两个方法:doGetAuthenticationInfo(获取即将需要认真的信息)、doGetAuthorizationInfo(获取通过认证后的权限信息)。

HashedCredentialsMatcher,凭证匹配器,用于告诉Shiro在认证时通过什么方式(算法)来匹配密码。默认(storedCredentialsHexEncoded=false)Base64编码,可以修改为(storedCredentialsHexEncoded=true)Hex编码。

LifecycleBeanPostProcessor,Shiro生命周期处理器,保证实现了Shiro内部lifecycle函数的bean执行。

开启Shiro的注解功能(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证,需要配置两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)实现此功能。

其它的就是缓存管理,记住登录、验证码、分布式系统共享Session之类的,这些大部分都是需要自己进行的实现,其中缓存管理,记住登录比较简单实现,并需要注入到SecurityManager让Shiro的安全管理器进行管理就好了。后续章节中会一一补充。

下面使用SpringBoot集成Shiro完成一个比较简单的安全验证(传统XML方式配置,请点击这里):

步骤1:首先创建一个Maven工程,在pom.xml中添加shiro相关依赖包:







org.springframework.boot

spring-boot-starter-thymeleaf







org.apache.shiro

shiro-spring

1.2.5







com.github.theborakompanioni

thymeleaf-extras-shiro

1.2.1





目录结构图(其中RetryLimitHashedCredentialsMatcher.java在本例中为使用):



本案例的工程目录结构图

步骤2:实现用户、角色的CRUD的相关类(domain,dao/mapper/repository,service),由于篇幅和重要点关系,这些就省略了。

步骤3:实现Realm,继承AuthorizingRealm,并重写doGetAuthorizationInfo(用于获取认证成功后的角色、权限等信息)和doGetAuthenticationInfo(验证当前登录的Subject)方法



publicclassUserRealmextendsAuthorizingRealm{

@Resource

privateUserServiceuserService;

@Resource

privateRoleServiceroleService;



@Override

protectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){

StringcurrentLoginName=(String)principals.getPrimaryPrincipal();

ListuserRoles=newArrayList();

ListuserPermissions=newArrayList();

//从数据库中获取当前登录用户的详细信息

Useruser=userService.findByLoginName(currentLoginName);

if(null!=user){

//获取当前用户下所有ACL权限列表待续。。。

//获取当前用户下拥有的所有角色列表

Listroles=roleService.findByUserId(user.getId());

for(inti=0;i
userRoles.add(roles.get(i).getCode());

}

}else{

thrownewAuthorizationException();

}

System.out.println("#######获取角色:"+userRoles);

System.out.println("#######获取权限:"+userPermissions);

//为当前用户设置角色和权限

SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();

authorizationInfo.addRoles(userRoles);

authorizationInfo.addStringPermissions(userPermissions);

returnauthorizationInfo;

}



/

验证当前登录的Subject

LoginController.login()方法中执行Subject.login()时执行此方法

/

@Override

protectedAuthenticationInfodoGetAuthenticationInfo(

AuthenticationTokenauthcToken)throwsAuthenticationException{

System.out.println("###【开始认证[SessionId]】"+SecurityUtils.getSubject().getSession().getId());

StringloginName=(String)authcToken.getPrincipal();

Useruser=userService.findByLoginName(loginName);

if(user==null){

thrownewUnknownAccountException();//没找到帐号

}

//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现

SimpleAuthenticationInfoauthenticationInfo=newSimpleAuthenticationInfo(

user.getUserName(),//用户名

user.getPassword(),//密码

ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt,采用明文访问时,不需要此句

getName()//realmname

);

returnauthenticationInfo;

}

}



步骤4:创建Shiro配置类:ShiroConfiguration,这是最重要的,用于替代XML配置的JavaConfig,详细说明,请看代码中注释。



@Configuration

publicclassShiroConfiguration{

privatestaticfinalLoggerlogger=LoggerFactory.getLogger(ShiroConfiguration.class);



/

Shiro的Web过滤器Factory命名:shiroFilter




@paramsecurityManager

@return

/

@Bean(name="shiroFilter")

publicShiroFilterFactoryBeanshiroFilterFactoryBean(SecurityManagersecurityManager){

logger.info("注入Shiro的Web过滤器-->shiroFilter",ShiroFilterFactoryBean.class);

ShiroFilterFactoryBeanshiroFilterFactoryBean=newShiroFilterFactoryBean();



//Shiro的核心安全接口,这个属性是必须的

shiroFilterFactoryBean.setSecurityManager(securityManager);

//要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面

shiroFilterFactoryBean.setLoginUrl("/login");

//登录成功后要跳转的连接,逻辑也可以自定义,例如返回上次请求的页面

shiroFilterFactoryBean.setSuccessUrl("/index");

//用户访问未对其授权的资源时,所显示的连接

shiroFilterFactoryBean.setUnauthorizedUrl("/pages/403");

/定义shiro过滤器,例如实现自定义的FormAuthenticationFilter,需要继承FormAuthenticationFilter

本例中暂不自定义实现,在下一节实现验证码的例子中体现

/



/定义shiro过滤链Map结构

Map中key(xml中是指value值)的第一个''/''代表的路径是相对于HttpServletRequest.getContextPath()的值来的

anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的表示参数,比方说login.jsp?main这种

authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter

/

MapfilterChainDefinitionMap=newLinkedHashMap();

//配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了

filterChainDefinitionMap.put("/logout","logout");



//:这是一个坑呢,一不小心代码就不好使了;

//

filterChainDefinitionMap.put("/webui/","anon");

filterChainDefinitionMap.put("/webjars/","anon");

filterChainDefinitionMap.put("/login","anon");

filterChainDefinitionMap.put("/","authc");



shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);



returnshiroFilterFactoryBean;

}



/

不指定名字的话,自动创建一个方法名第一个字母小写的bean

@Bean(name="securityManager")

@return

/

@Bean

publicSecurityManagersecurityManager(){

logger.info("注入Shiro的Web过滤器-->securityManager",ShiroFilterFactoryBean.class);

DefaultWebSecurityManagersecurityManager=newDefaultWebSecurityManager();

securityManager.setRealm(userRealm());

returnsecurityManager;

}



/

ShiroRealm继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的



@paramcacheManager

@return

/

@Bean

publicUserRealmuserRealm(){

UserRealmuserRealm=newUserRealm();

//告诉realm,使用credentialsMatcher加密算法类来验证密文

userRealm.setCredentialsMatcher(hashedCredentialsMatcher());

userRealm.setCachingEnabled(false);

returnuserRealm;

}



/

凭证匹配器

(由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了

所以我们需要修改下doGetAuthenticationInfo中的代码;



可以扩展凭证匹配器,实现输入密码错误次数后锁定等功能,下一次

@return

/

@Bean(name="credentialsMatcher")

publicHashedCredentialsMatcherhashedCredentialsMatcher(){

HashedCredentialsMatcherhashedCredentialsMatcher=newHashedCredentialsMatcher();



hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;

hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于md5(md5(""));

//storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码

hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);



returnhashedCredentialsMatcher;

}



/

Shiro生命周期处理器

@return

/

@Bean

publicLifecycleBeanPostProcessorlifecycleBeanPostProcessor(){

returnnewLifecycleBeanPostProcessor();

}

/

开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证

配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能

@return

/

@Bean

@DependsOn({"lifecycleBeanPostProcessor"})

publicDefaultAdvisorAutoProxyCreatoradvisorAutoProxyCreator(){

DefaultAdvisorAutoProxyCreatoradvisorAutoProxyCreator=newDefaultAdvisorAutoProxyCreator();

advisorAutoProxyCreator.setProxyTargetClass(true);

returnadvisorAutoProxyCreator;

}

@Bean

publicAuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(){

AuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor=newAuthorizationAttributeSourceAdvisor();

authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());

returnauthorizationAttributeSourceAdvisor;

}



/

添加ShiroDialect为了在thymeleaf里使用shiro的标签的bean

@return

/

@Bean(name="shiroDialect")

publicShiroDialectshiroDialect(){

returnnewShiroDialect();

}

}



步骤四5:实现Controller,登录/退出等操作。



@Controller

publicclassSecurityController{

privatestaticfinalLoggerlogger=LoggerFactory.getLogger(UserController.class);



@RequiresRoles("ADMIN")

@RequestMapping(value="/index",method=RequestMethod.GET)

publicStringindex(Modelmodel){

StringuserName=(String)SecurityUtils.getSubject().getPrincipal();

model.addAttribute("username",userName);

return"index";

}



@RequestMapping(value="",method=RequestMethod.GET)

publicStringdefaultIndex(Modelmodel){

StringuserName=(String)SecurityUtils.getSubject().getPrincipal();

model.addAttribute("username",userName);

return"index";

}



@RequestMapping(value="/login",method=RequestMethod.GET)

publicStringloginForm(Modelmodel){

model.addAttribute("user",newUser());

return"login";

}

@RequestMapping(value="/login",method=RequestMethod.POST)

publicStringlogin(@ValidUseruser,BindingResultbindingResult,RedirectAttributesredirectAttributes){

if(bindingResult.hasErrors()){

return"login";

}



Stringusername=user.getUserName();

System.out.println(username);

UsernamePasswordTokentoken=newUsernamePasswordToken(user.getUserName(),user.getPassword());

//获取当前的Subject

SubjectcurrentUser=SecurityUtils.getSubject();

try{

//在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查

//每个Realm都能在必要时对提交的AuthenticationTokens作出反应

//所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法

logger.info("对用户["+username+"]进行登录验证..验证开始");

currentUser.login(token);

logger.info("对用户["+username+"]进行登录验证..验证通过");

}catch(UnknownAccountExceptionuae){

logger.info("对用户["+username+"]进行登录验证..验证未通过,未知账户");

redirectAttributes.addFlashAttribute("message","未知账户");

}catch(IncorrectCredentialsExceptionice){

logger.info("对用户["+username+"]进行登录验证..验证未通过,错误的凭证");

redirectAttributes.addFlashAttribute("message","密码不正确");

}catch(LockedAccountExceptionlae){

logger.info("对用户["+username+"]进行登录验证..验证未通过,账户已锁定");

redirectAttributes.addFlashAttribute("message","账户已锁定");

}catch(ExcessiveAttemptsExceptioneae){

logger.info("对用户["+username+"]进行登录验证..验证未通过,错误次数过多");

redirectAttributes.addFlashAttribute("message","用户名或密码错误次数过多");

}catch(AuthenticationExceptionae){

//通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景

logger.info("对用户["+username+"]进行登录验证..验证未通过,堆栈轨迹如下");

ae.printStackTrace();

redirectAttributes.addFlashAttribute("message","用户名或密码不正确");

}

//验证是否登录成功

if(currentUser.isAuthenticated()){

logger.info("用户["+username+"]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");

return"/index";

}else{

token.clear();

return"redirect:/login";

}

}



@RequestMapping(value="/logout",method=RequestMethod.GET)

publicStringlogout(RedirectAttributesredirectAttributes){

//使用权限管理工具进行用户的退出,跳出登录,给出提示信息

SecurityUtils.getSwww.shanxiwang.netubject().logout();

redirectAttributes.addFlashAttribute("message","您已安全退出");

return"redirect:/login";

}



@RequestMapping("/pages/403")

publicStringunauthorizedRole(){

logger.info("------没有权限-------");

return"pages/403";

}

}



步骤6:前端页面,采用Thymeleaf引擎(包括后续章节,都是采用此引擎)

login.html:











用户登录


href="/webjars/bootstrap/css/bootstrap.min.css"th:href="@{/webjars/bootstrap/css/bootstrap.min.css}"/>













@












@










登录

















index.html:











首页







Hello,,howareyoutoday?




I''mfine,我拥有管理员角色--









403.html:











Inserttitlehere





您所访问的资源未被授权..







步骤7:启动应用,在浏览器中输入http://localhost:8080:

献花(0)
+1
(本文系网络学习天...首藏)