什么是Apache Shiro?
Apache Shiro是一个功能强大且灵活的开源安全框架,可以干净地处理身份验证,授权,企业会话管理和加密。
Apache Shiro的首要目标是易于使用和理解。安全有时可能非常复杂,甚至会很痛苦,但这不是必须的。框架应尽可能掩盖复杂性,并公开简洁直观的API,以简化开发人员确保其应用程序安全的工作。
Apache Shiro是具有许多功能的全面的应用程序安全框架。下图显示了Shiro集中关注的功能点:
Shiro所谓的“应用程序安全性的四个基石”为目标-身份验证,授权,会话管理和密码。
- Authentication(身份验证): 有时称为“登录”,这是证明用户是他们所说的身份的行为。
- Authorization(授权): 访问控制的过程,即确定“谁”有权访问“什么”。
- Session Management(会话管理): 即使在非Web或EJB应用程序中,也管理用户特定的会话。
- Cryptography(密码): 使用密码算法保持数据安全,同时仍易于使用。
在不同的应用程序环境中,还具有其他功能来支持和加强这些问题,尤其是:
- Web Support(Web支持):Shiro的Web支持API可帮助轻松保护Web应用程序。
- Caching(缓存):缓存是Apache Shiro API的第一层公民,可确保安全操作保持快速有效。
- Concurrency(并发性):Apache Shiro的并发功能支持多线程应用程序。
- Testing(测试):测试支持可以帮助您编写单元测试和集成测试,并确保您的代码将按预期进行保护。
- “Run As”(运行方式):一种功能,允许用户采用其他用户的身份(如果允许),有时在管理方案中很有用。
- “Remember Me”(记住我):在整个会话中记住用户的身份,因此他们仅在必要时登录。
身份验证
身份验证是身份验证的过程-也就是说,证明用户实际上就是他们所说的身份。为了使用户证明自己的身份,他们需要提供一些标识信息以及系统可以理解和信任的那种身份证明。
这是通过向Shiro提交用户的主体和凭据来完成的,以查看它们是否与应用程序期望的匹配。
Principal 是受试者的“识别属性”。校长可以是可以识别主题的任何东西,例如名字(姓氏),姓氏(姓氏或姓氏),用户名,社会保险号等。当然,诸如姓氏之类的东西并不擅长唯一地标识a
Subject,因此用于身份验证的最佳主体对于应用程序是唯一的-通常是用户名或电子邮件地址。
Credentials 通常是仅由知道的秘密值,Subject它们被用作证明它们实际上“拥有”所主张身份的支持证据。凭据的一些常见示例是密码,生物特征数据(例如指纹和视网膜扫描)以及X.509证书。
主体/凭证配对的最常见示例是用户名和密码。用户名是声明的身份,密码是与声明的身份匹配的证明。如果提交的密码与应用程序期望的密码匹配,则该应用程序可以在很大程度上假定用户确实是他们所说的真实身份,因为没有其他人应该知道相同的密码。
验证Subjects
身份验证的过程Subject可以有效地分为三个不同的步骤:
- 收集主题提交的主体和证书
- 提交主体和凭据以进行身份验证。
- 如果提交成功,则允许访问,否则重试身份验证或阻止访问。
Spring boot 集成 Shiro
- 依赖包(身份验证)
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>LATEST</version>
</dependency>
- 配置文件shiroConfig:
package com.anvy.shiro.config;
import com.anvy.shiro.realm.UserRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Resource
private UserRealm userRealm;
//配置ShiroFilterFactoryBean过滤策略。
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
factoryBean.setLoginUrl("/login");
// factoryBean.setUnauthorizedUrl("");
Map<String,String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/login", "anon");
filterMap.put("/swagger*/**", "anon");
filterMap.put("/webjars/**", "anon");
filterMap.put("/v2/**", "anon");
filterMap.put("/**", "authc");
factoryBean.setFilterChainDefinitionMap(filterMap);
return factoryBean;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
}
Shiro自带的过滤器有:
//org.apache.shiro.web.filter.mgt.DefaultFilter
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
authcBearer(BearerHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class),
ShiroConfig中的filterMap可以设置访问权限,针对访问URL设置不同的拦截权限。
- 自定义Realm。
package com.anvy.shiro.realm;
import com.anvy.mybatis.entity.PetUser;
import com.anvy.mybatis.service.IPetUserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class UserRealm extends AuthorizingRealm {
@Autowired
private IPetUserService petUserService;
/**
* 权限
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 身份认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String userName = (String) authenticationToken.getPrincipal();
if(StringUtils.isBlank(userName)){
throw new AccountException("账户不正确!");
}
String password = (String) authenticationToken.getCredentials();
if(StringUtils.isBlank(password)){
throw new AccountException("密码不正确!");
}
PetUser petUser = new PetUser(userName);
QueryWrapper<PetUser> query = new QueryWrapper<PetUser>(petUser);
PetUser one = petUserService.getOne(query);
if(one == null){
throw new UnknownAccountException("未知登陆用户~");
}
if(!password.equals(one.getUserPassword())){
throw new AuthenticationException("密码验证错误~");
}
return new SimpleAuthenticationInfo(userName,password,getName());
}
}
- 书写controller进行登录操作。
package com.anvy.petcare.modules.user.controller;
import com.anvy.dto.ResultVo;
import com.anvy.mybatis.entity.PetUser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author Anvy Lao
* @since 2021-01-16
*/
@Api("登陆控制")
@Slf4j
@RestController
@RequestMapping("/user")
public class PetUserController {
@ApiOperation(value = "用户登录",notes = "用户")
@RequestMapping(value = "login",method = RequestMethod.POST)
public ResultVo login(PetUser user){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),user.getUserPassword());
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return ResultVo.error().message("未知账户");
} catch (IncorrectCredentialsException ice) {
return ResultVo.error().message("密码不正确~");
} catch (LockedAccountException lae) {
return ResultVo.error().message("账户已锁定~");
} catch (ExcessiveAttemptsException eae) {
return ResultVo.error().message("用户名或密码错误次数过多");
} catch (AuthenticationException ae) {
return ResultVo.error().message("用户名或密码不正确!");
}
if (subject.isAuthenticated()) {
return ResultVo.success().message("登录成功~");
} else {
token.clear();
return ResultVo.error().message("登录失败~");
}
}
}
swagger进行登录操作,会调用Realm中的doGetAuthenticationInfo方法进行验证操作。