前言
通过整合shiro来实现登录功能。
shiro有三个核心组件:Subject, SecurityManager 和 Realms.
- Subject:代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
- SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
(Facade模式
:是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节。) - Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
注意:在下面的步骤中,我们会主要先实现认证,最后在认证的基础上增加资源授权功能
一、引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
二、编写UserRealm类(仅认证)
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;//用于mybatis查询数据库用户信息的服务层
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
String username=token.getUsername();
User userByName = userService.findUserByName(username);
//如果用户根本不存在
if(userByName==null)
{
return null; //如果返回null,会抛出UnknownAccountException
}
//将真正的密码给到shiro 他会自动帮我们处理
return new SimpleAuthenticationInfo("",userByName.getPassword(),"");
}
}
需要注意的是,在上面,如果用户不存在,我们直接返回null即可,shiro会自动抛出UnknownAccountException
;如果用户存在但密码错误会抛出IncorrectCredentialsException
,密码正确则不会抛出异常。
至于抛出这两个异常有什么用呢,我们后面再说。
三、创建shiroConfig配置类(仅认证)
@Configuration
public class shiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager)
{
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加shiro内置过滤器
//常用的过滤器
//anon 无需认证
//authc 必须认证才可以访问
//user 如果使用rememberme 的功能可以直接访问
//perms 该资源必须授权资源权限才可以访问
//role 必须得到角色权限才可以访问
Map<String,String> filterMap = new LinkedHashMap<String,String>();
//这里将add和update这种操作拦截
filterMap.put("/html/addUserShiro.html","authc");
filterMap.put("/html/updateUserShiro.html","authc");
//设置拒绝后返回页面
shiroFilterFactoryBean.setLoginUrl("/html/loginShiro.html");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name ="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm)
{
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
/**
* 创建realm
*/
@Bean(name ="userRealm") //使用Bean注解:方法返回的对象给spring管理
public UserRealm getUserRealm()
{
return new UserRealm();
}
}
四、编写Controller方便测试
@RequestMapping(value="/loginShiro" )
@ResponseBody
public RespBean loginShiro(HttpServletRequest httpServletRequest) throws IOException {
String username=httpServletRequest.getParameter("username");
String pwd=httpServletRequest.getParameter("password");
//1. 获取subject
Subject subject = SecurityUtils.getSubject();
//2. 封装用户数据
UsernamePasswordToken token=new UsernamePasswordToken(username,pwd);
//3.执行登录判断
try {
subject.login(token);
return RespBean.ok("success");
}catch (UnknownAccountException e)
{
return RespBean.error("账户不存在!");
}catch (IncorrectCredentialsException e)
{
return RespBean.error("密码错误!");
}
}
由于shiro会根据不同情况抛出UnknownAccountException
和IncorrectCredentialsException
异常,我们在上面的controller中进行捕获即可。
五、进行postman测试简单登录
我们对登录的url进行postman测试,数据库预先存储了用户名和密码都为wuyuehang的用户信息。
当登录账号密码都正确时
这个时候controller中不会抛出异常,所以登陆成功。当登录用户不存在时,我们在前面直接返回了null,shiro将抛出UnknownAccountException
异常,从而被controller捕获,返回账户不存在的信息。
当用户密码错误时,输入的密码被交给shiro,shiro发现密码错误,会抛出IncorrectCredentialsException
异常,所以在controller中被捕获,从而返回密码错误的提示信息。
这是shiro与登录的结合。
六、实际进行认证测试
接下来,我们将测试这样一个功能:
- 若直接访问authc类型的界面,由于未被认证将自动跳转到登录界面。
- 在经过登录后,通过认证,便可以访问authc界面
我们在shiroConfig类中,对"/html/addUserShiro.html"
和"/html/updateUserShiro.html"
两个路径拦截,并将这两个路径指定为必须经过认证后才可以正常访问。下面我们对其进行测试
- 首先直接访问
"/html/addUserShiro.html"
,因为还没有认证,就会自动跳转到我们设定好的"/html/loginShiro.html"
- 在被跳转的登陆界面上进行登录,登录成功后会跳转到我们一个测试界面
- 这两个的超链接分别对应前面我们设置authc,即需要认证的拦截界面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查询成绩</title>
</head>
<body>
<a href="/html/addUserShiro.html">add</a>
<a href="/html/updateUserShiro.html">update</a>
</body>
</html>
随便点击一个后,我们可以发现经过登录认证后,我们进来了。
至此,用户经过认证(即登录成功)后,才可以访问被拦截的界面.
下一步,让我们来实现shiro的授权操作,授权的应用场景在于,某些用户尽管通过认证,但是我们依旧要对权限加以控制。
七、进行授权的设置
与认证一样,我们首要做的便是拦截器的设置,以资源授权perms为例,我们添加资源授权的拦截,
在shiroconfig类中的filterMap添加
filterMap.put("/html/addUserShiro.html","perms[user_add]");
user_add字符串任意指定,但是这里字符串是什么后面,会需要。
这时,我们通过登录来完成认证之后,再点击/html/addUserShiro.html
这个路径,便会出现如下界面,原先经过认证后能访问的资源界面就行不通了。
原因是:未授权,同样, 我们亦可以自定义一个未授权html界面,如下所示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>拒绝访问</title>
</head>
<body>
<h1>您好,您没有访问该界面的权限!</h1>
</body>
</html>
在shiroconfig类中添加
shiroFilterFactoryBean.setUnauthorizedUrl("/html/Unauthorized.html");
再次测试
我们接下来在UserRealm类的doGetAuthorizationInfo方法中编写授权的逻辑代码
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
info.addStringPermission("user_add");
return info;
}
这样,我们完成了资源的授权,跑一下测试
在经过认证后,本来不可以被访问,在该资源被授权后,访问html资源成功。