Apache Shiro 是一个功能强大且易于使用的Java安全框架,可执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的web和企业应用程序。
下面即springboot集成shiro,实现用户登录认证功能
1.原理
shiro外部架构如下:
主要包括三大实体:Subject、Realm、和SecurityManager。
Subject即当前用户,我们可以通过Subject自带的 SecurityUtils.getSubject()方法获取当前对象,并通过当前用户拿到shiro的session,进行后续的认证授权等。
SecurityManager即安全管理器,可以视为拦截器,拦截请求,并转至shiro中处理。
Realm,可以有1个或多个Realm,即安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;一般由用户自定义实现。
shiro实现过程一般如下,编写shiro配置文件,配置对应的加密方式,并注册进Realm中,然后把对应的realm注入shiro配置中进行注册;编写Realm类,实现对应的登录认证、授权等功能。
2.注入依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
3.编写shiro配置文件
@Configuration
public class ShiroConfig {
//此处用于实现授权功能,配置需要拦截的接口
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//拦截页面
Map<String, String> filterMap = new LinkedHashMap<>();
//登录/登出,所有人的权限
filterMap.put("/user/login", "anon");
filterMap.put("/user/logout", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//未登录页面跳转
shiroFilterFactoryBean.setLoginUrl("/user/show");
//未有权限页面跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauthorized");
return shiroFilterFactoryBean;
}
//注入对应的userRealm类
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager SecurityManager = new DefaultWebSecurityManager();
SecurityManager.setRealm(userRealm);
return SecurityManager;
}
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
//注册MD5加密
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* 设置shiro加密方式
*
* @return HashedCredentialsMatcher
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 使用md5 算法进行加密
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 设置散列次数: 意为加密几次
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
}
4.编写userRealm类,实现登录拦截
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserManageService userManageService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
/**配置权限
*此处User实体需配置属性roles,用户权限
*获取当前用户对象
* */
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Subject subject= SecurityUtils.getSubject();
UserAccount currentUser =(UserAccount) subject.getPrincipal();
authorizationInfo.addStringPermission(currentUser.getRoles());
log.info("用户权限为:"+currentUser.getRoles());
return authorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//用户名/密码认证
//从接口处获取得到的用户名
String username = token.getUsername();
//调用mybatis_plus中的方法,查询数据库中用户名对应的数据
QueryWrapper<UserAccount> wrapper = new QueryWrapper<>();
wrapper.eq("Username",username);
UserAccount user=userManageService.getOne(wrapper);
//为空,即用户名不存在
if(user==null){
return null;
}else {
log.info(user.getUsername());
}
//principal:认证的实体信息,可以是username,也可以是数据库表对应的用户的实体对象
Object principal = user.getUsername();
return new SimpleAuthenticationInfo(user, user.getPassword() , ByteSource.Util.bytes(principal),getName());
}
}
注意:此处进行登录验证时使用了MD5加密,即数据库中存储的密码应是MD5加密后的密文密码,而不是明文密码。
示例如下:
//md5加密
admin.setPassword(new Md5Hash(admin.getPassword(), admin.getUsername(),2).toString());
5.编写controller接口
//用户登录(使用用户名)
@PostMapping("/login")
public CommonResult<Object> UserLogin(@RequestBody UserAccount user)
{
String username=user.getUsername();
String password=user.getPassword();
//shiro验证
Subject subject= SecurityUtils.getSubject();
//根据用户名密码生成一个令牌
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
try {
subject.login(token); //执行登录操作
} catch (UnknownAccountException e) {
log.info("登录用户不存在");
return new CommonResult<>(416,"用户不存在",username);
} catch (IncorrectCredentialsException e) {
log.info("登录密码错误");
return new CommonResult<>(412,"密码错误,请重新登录",password);
}catch (AuthenticationException e) {
log.warn("用户登录异常:" + e.getMessage());
return new CommonResult<>(416,"账户异常",username);
}
return new CommonResult<>(200,"登录成功",username);
}