先放shiro的执行流程,可以更好的理解接下来的代码逻辑
ApplicationCode是用户自己编写的代码;
Subject :就是 shiro 管理的用户,所有的subject实例都会被绑定到一个SecurityManager上,我们与一个subject交互,在底层shiro会自动转换成与一个SecurityManager的交互;
SecurityManager :安全管理器,是 shiro 权限控制核心对象, 在编程时,只需要操作Subject 方法, 底层调用 SecurityManager 方法,无需直接编程操作 SecurityManager;
Realm :Shiro中作为应用程序和安全数据之间的“桥梁”或“连接器”。他获取安全数据来判断subject是否能够登录,subject拥有什么权限。他有点类似DAO。在配置realms时,需要至少一个realm。而且Shiro提供了一些常用的。
登录流程: 应用程序 --- Subject --- SecurityManager --- Realm --- 安全数据
身份认证和授权的区别:简单来说就是用户登录成功后即表明认证通过可以访问一些需要认证通过才能访问的页面,而授权则是当用户需要做某些操作(一般来说就是数据的增删改查),这个时候就需要该用户包含某些权限,这些权限在shiro中以数据库中某个特定的字段包含特定的值来实现。因此就引出了权限和角色的概念,当某个用户被设定为某种角色的时候,才可以执行该角色具备的权限,具体逻辑如下图:
那么就先来看看身份认真怎么实现。(shiro的相关配置就不放出来了)
登陆就离不开表单提交,我们需要配置一个action类接受前台提交的用户名和密码
@Namespace("/")
@Controller
@Scope("prototype")
@ParentPackage("json-default")
public class UserAction extends BaseAction<User> {
@Action(value = "user_login", results = {
@Result(name = "login", type = "redirect", location = "login.html"),
@Result(name = "success", type = "redirect", location = "index.html") })
public String login() {
//基于shiro实现登录
//固定代码,获取subject实例
Subject subject = SecurityUtils.getSubject();
//将用户名和密码信息封装到AuthenticationToken中
AuthenticationToken usernamePasswordToken = new UsernamePasswordToken(model.getUsername(),model.getPassword());
try {
subject.login(usernamePasswordToken);
return SUCCESS;
} catch (AuthenticationException e) {
e.printStackTrace();
return LOGIN;
}
}
}
这里的subject调用login(token)方法实际是调用SecurityManager的login(token),SecurityManager接受到token(令牌)信息后会委托内置的Authenticator的实例(通常都是ModularRealmAuthenticator类的实例)调用authenticator.authenticate(token). ModularRealmAuthenticator在认证过程中会对设置的一个或多个Realm实例进行适配。
如果在应用程序中配置了多个Realm,ModularRealmAuthenticator会根据配置的AuthenticationStrategy(认证策略)来进行多Realm的认证过程。在Realm被调用后,AuthenticationStrategy将对每一个Realm的结果作出响应。 注:如果应用程序中仅配置了一个Realm,Realm将被直接调用而无需再配置认证策略。
判断每一个Realm是否支持提交的token,如果支持,Realm将调用getAuthenticationInfo(token); getAuthenticationInfo 方法就是实际认证处理,我们通过覆盖Realm的doGetAuthenticationInfo方法来编写我们自定义的认证处理。
自定义的Realm需要继承Realm接口下面的实现类AuthorizingRealm类,通过重写doGetAuthenticationInfo(AuthenticationToken token)方法自定义认证处理。
@Service
@Transactional
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserRepository userRepository;
@Override
// 认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
User user = userRepository.findByUsername(usernamePasswordToken.getUsername());
if(user==null) {
//用户不存在
//参数一:期望登录后,存在subject中的信息
//参数二:密码,用户名不存在会报用户名不存在异常
//参数三:realm名称
return null;
}else {
//返回用户密码,securityManager安全管理器会自动比较返回密码与用户输入密码是否一致,如果不一致就会报密码错误异常
return new SimpleAuthenticationInfo(user, user.getPassword(),getName());
}
}
@Override
// 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
return null;
}
}
通过用户名称查询数据库中的用户如果用户不存在就会抛出用户不存在异常,当返回的密码和SecurityManager中的用户输入密码不一致的时候就会抛出错误凭证异常。
- org.apache.shiro.authc.UnknownAccountException 当认证方法直接返回 null,说明用户名不存在。
- org.apache.shiro.authc.IncorrectCredentialsException 当返回对象中密码与用户输入密码不一致。
将Realm注入到SecurityManager中
当用户认证通过后,subject会将用户信息存入到session中,这样就可以实现身份认证,在未认证的情况下只能访问登录界面。