shiro

基本功能

在这里插入图片描述

shiro 完成工作的流程

在这里插入图片描述

架构

在这里插入图片描述

主要组件

  • SecurityManager:安全管理器,Shiro 最核心的组件。
  • Authenticator:认证器,认证 AuthenticationToken 是否有效。
  • AuthenticationToken:认证信息,比如用户名和密码。
  • SessionManager:会话管理器,会话 Session 就是用户使用程序的一定时间内携带的数据。
  • Subject:当前操作主体,即用户。
  • SubjectContext:用户的上下文数据对象。
  • ThreadContext:线程上下文对象,负责绑定 Subject 对象到当前线程。

SecurityManager

在这里插入图片描述

继承了授权器、认证器、会话管理器三个接口,用于提供各种安全管理的服务。
SecurityManager 提供三个方法:登录、退出登录、创建 Subject

public interface SecurityManager extends Authenticator, Authorizer, SessionManager {

	Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;

	void logout(Subject subject);

	Subject createSubject(SubjectContext context);
}

如何获取 Subject 对象

Shiro 提供了一个工具类:SecurityUtils,用于获取 SecurityManagerSubject 对象。

public static Subject getSubject() {
	// 1、从线程上下文对象中获取
    Subject subject = ThreadContext.getSubject();
    // 2、如果没有,就构造一个,再绑定到线程上下文对象中
    if (subject == null) {
    	// 使用 Subject的内部类Builder构造
        subject = (new Subject.Builder()).buildSubject();
        ThreadContext.bind(subject);
    }
    return subject;
}

其中,从线程上下文对象中获取:

public abstract class ThreadContext {
    // ThreadContext_SUBJECT_KEY
	public static final String SUBJECT_KEY = ThreadContext.class.getName() + 											"_SUBJECT_KEY";

	// 使用一个 ThreadLocal 对象来保存 Subject对象
	private static final ThreadLocal<Map<Object, Object>> resources = new 					InheritableThreadLocalMap<Map<Object, Object>>();

    public static Subject getSubject() {
       // 从resources中获取
       return (Subject) get(SUBJECT_KEY);
    }
}

如果没有,构造一个 Subject 对象:

public Subject buildSubject() {
	// 最终使用安全管理器创建
    return this.securityManager.createSubject(this.subjectContext);
}

绑定:实际上就放入线程上下文对象中的 ThreadLocal 成员

public static void bind(Subject subject) {
    if (subject != null) {
        put(SUBJECT_KEY, subject);
    }
}

大致流程:SubjectContext 怎么来的 ?
在这里插入图片描述

会话管理器

会话接口 Session :用于保存用户的会话数据。

public interface Session {
	Serializable getId();
	Date getStartTimestamp();
	Date getLastAccessTime();
	long getTimeout() throws InvalidSessionException;
	void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;
	String getHost();
	void touch() throws InvalidSessionException;
	void stop() throws InvalidSessionException;
	Collection<Object> getAttributeKeys() throws InvalidSessionException;
	Object getAttribute(Object key) throws InvalidSessionException;
	void setAttribute(Object key, Object value) throws InvalidSessionException;
	Object removeAttribute(Object key) throws InvalidSessionException;
}

常用的实现:
在这里插入图片描述

会话管理器的方法:开启会话和创建会话

public interface SessionManager {
	// SessionContext:创建会话时的上下文对象,用于保存会话的属性,实际上就是一个map
	Session start(SessionContext context);
	Session getSession(SessionKey key) throws SessionException;
}

会话管理器主要用于创建、删除、操作会话。常见实现:

在这里插入图片描述

一般使用 DefaultWebSessionManager

会话管理器的基础实现:AbstractNativeSessionManager

public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager, EventBusAware {
	private EventBus eventBus;
	// 使用Session监听器对Session的状态进行监听
	private Collection<SessionListener> listeners;
	
	// 根据SessionContext开启一个新的Session
    public Session start(SessionContext context) {
       // createSession()是一个抽象方法,由子类实现
       Session session = createSession(context);
       applyGlobalSessionTimeout(session);
       // 钩子方法,空实现,留给子类重写
       onStart(session, context);
       // 遍历每一个监听器,对session调用onStart()方法
       notifyStart(session);
       return createExposedSession(session, context);
    } 
    protected void applyGlobalSessionTimeout(Session session) {
       session.setTimeout(getGlobalSessionTimeout());
       // 钩子方法
       onChange(session);
	}
    protected Session createExposedSession(Session session, SessionContext context) {
        return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
    }
    ...
}

1、定义了开启新会话的方法 start() 的骨架
2、创建 Session 对象的方法由子类实现,子类可以创建不同的实例
3、这里保留了两个钩子方法:onStart()onChange(),可以由子类扩展
4、start() 方法不会直接将创建的 Session 对象返回,而是交给 DelegatingSession 管理,构造器:将当前会话管理器对象、SessionsessionId 作为参数

protected Session createExposedSession(Session session, SessionKey key) {
    // SessionKey实际就是 sessionId 的对象形式
    return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
}

AbstractNativeSessionManager 获取 Session 的方法:

public Session getSession(SessionKey key) throws SessionException {
    Session session = lookupSession(key);
    return session != null ? createExposedSession(session, key) : null;
}

private Session lookupSession(SessionKey key) throws SessionException {
    if (key == null) {
        throw new NullPointerException("SessionKey argument cannot be null.");
    }
    // 抽象方法
    return doGetSession(key);
}

确保不会直接将 Session 对象暴露出去,而是暴露一个 DelegatingSession 对象。

监听器相关的三个方法: Session 启动、停止、过期的时候,会回调这几个方法

protected void notifyStart(Session session) {
    for (SessionListener listener : this.listeners) {
        listener.onStart(session);
    }
}

protected void notifyStop(Session session) {
    Session forNotification = beforeInvalidNotification(session);
    for (SessionListener listener : this.listeners) {
        listener.onStop(forNotification);
    }
}

protected void notifyExpiration(Session session) {
    Session forNotification = beforeInvalidNotification(session);
    for (SessionListener listener : this.listeners) {
        listener.onExpiration(forNotification);
    }
}

Session 实例是怎么创建出来的 ?

SessionContext 的注释:

USAGE: Most Shiro end-users will never use a SubjectContext instance directly and instead will call the Subject.getSession() or Subject.getSession(boolean) methods (which will usually use SessionContext instances to start a session with the application’s SessionManager.

大部分的 shiro 使用者不会直接使用 SubjectContext 来创建 Session 对象,而是使用SubjectgetSession() 方法来创建。DelegatingSubject 的实现:

public Session getSession() {
    return getSession(true);
}
/**
* 返回与当前Subject关联的Session对象,如果没有,根据 create 来决定是否创建一个新的会话对象
*/
public Session getSession(boolean create) {
	...
	if (this.session == null && create) {
		...
		// 1、创建一个DefaultSessionContext对象
		// 如果this.host不为空,就设置进去
		SessionContext sessionContext = createSessionContext();
		// 2、见AbstractNativeSessionManager的实现
		Session session = this.securityManager.start(sessionContext);
		// 3、封装成StoppingAwareProxiedSession对象
		this.session = decorate(session);
	}
	return this.session;
}

1、SessionContext 会直接创建一个 DefaultSessionContext 的实现,实际上就是一个 map ,只是提供了一些方法来方便获取属性。
2、调用安全管理器的 start() 方法来创建 Session 对象,AbstractNativeSessionManager 中定义了 start() 方法的基本框架:

public Session start(SessionContext context) {
	// 真正创建Session实例的方法
	// 抽象方法,有子类实现
    Session session = createSession(context);
    // 设置Session的超时时间
    // 每次Session发生属性的修改时,都会调用 onChange() 钩子方法,由子类扩展
    applyGlobalSessionTimeout(session);
    onStart(session, context);
    notifyStart(session);
    // 会将当前的Session封装成一个DelegatingSession对象返回,不会暴露当前的Session
    return createExposedSession(session, context);
}

子类对 createSession() 的实现:AbstractValidatingSessionManager

protected Session createSession(SessionContext context) throws AuthorizationException {
	// 规定在创建之前做一次校验
    enableSessionValidationIfNecessary();
    // 抽象方法
    return doCreateSession(context);
}

DefaultSessionManager 类对 doCreateSession() 的实现:

protected Session doCreateSession(SessionContext context) {
	// getSessionFactory().createSession(context)
	// 由会话工厂来创建Session对象
    Session s = newSessionInstance(context);
    ...
    // 使用SessionDAO来将会话对象Session保存起来
    create(s);
    return s;
}

在这里插入图片描述

对于默认的会话管理器:DefaultSessionManager,构造器:

public DefaultSessionManager() {
    this.deleteInvalidSessions = true;
    this.sessionFactory = new SimpleSessionFactory();
    this.sessionDAO = new MemorySessionDAO();
}

默认使用 SimpleSessionFactory 作为Session工厂,创建的Session实现为 SimpleSession 对象,使用 MemorySessionDAO 作为 SessionDAO 的实现,将每一个用户的 Session 保存在内存中:

public class MemorySessionDAO extends AbstractSessionDAO {
    private ConcurrentMap<Serializable, Session> sessions;
    public MemorySessionDAO() {
        this.sessions = new ConcurrentHashMap<Serializable, Session>();
    }
    ....
}

3、将 Session 对象封装成 StoppingAwareProxiedSession 返回



认证器接口

public interface Authenticator {
	// 认证方法
    public AuthenticationInfo authenticate(AuthenticationToken authenticationToken) throws AuthenticationException;
}

AuthenticationToken接口:保存了当前用户的待认证信息

public interface AuthenticationToken extends Serializable {
	Object getPrincipal();
	Object getCredentials();
}

认证通过后,生成一个 AuthenticationInfo 对象:

public interface AuthenticationInfo extends Serializable {
	PrincipalCollection getPrincipals();
	Object getCredentials();
}

Realm 接口

可以理解成一个 DAO,主要用于身份验证和授权,一般在项目中自定义一个,然后配置到安全管理器中去,安全管理器就会使用它访问数据库,如:

@Bean
public DefaultWebSecurityManager securityManager(ShiroRealm shiroRealm,
                                                 @Nullable RedisCacheManager redisCacheManager,
                                                 @Nullable EhCacheManager ehCacheManager,
                                                 DefaultWebSessionManager sessionManager) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    // 配置 SecurityManager,并注入 shiroRealm
    securityManager.setRealm(shiroRealm);
	// 其他配置...
    return securityManager;
}

Realm 接口:

public interface Realm {
	String getName();
	boolean supports(AuthenticationToken token);
	// 重点方法
	// 根据 token 的信息去访问数据库,构造出 AuthenticationInfo 对象并返回
	AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}

继承关系:一般会自定义一个 AuthorizingRealm的子类,注入 Spring IOC 容器,Shiro 提供的其他实现不够实用。
在这里插入图片描述

AuthorizingRealm 源码:

public abstract class AuthorizingRealm  extends AuthenticatingRealm....{
	// 额外定义了授权方法
	protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
	....
}

Subject 接口

表示当前正在访问程序的用户。接口:

public interface Subject {
	// 1、认证相关 ======================================================
	// 返回用户的唯一标识,比如用户名
	Object getPrincipal();
	PrincipalCollection getPrincipals();
	// 是否已经认证通过
	boolean isAuthenticated();
	// 认证登录
	void login(AuthenticationToken token) throws AuthenticationException;
	// 认证登出 
	void logout();
	
	// 2、授权相关 =====================================================
	// 是否有 permission 权限
	boolean isPermitted(String permission);
	// 检测是否有权限,如果没有将抛出异常
	void checkPermission(String permission) throws AuthorizationException;
	// ....
	
	// 3、Session 相关 ================================================
	Session getSession(); // 获取,不存在就创建

	// 内部类:构造器
	public static class Builder {
		private final SubjectContext subjectContext;
		private final SecurityManager securityManager;
		...
	}
	...
}

认证流程

使用 Shiro 进行认证:

// 1、根据输入的参数:用户名、密码,来构造一个 UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken
								(username, password, rememberMe); //host=null
// 2、登录
SecurityUtils.getSubject().login(token);

UsernamePasswordToken 的继承关系:
在这里插入图片描述

构造器:

public UsernamePasswordToken(final String username, final char[] password,
                            final boolean rememberMe, final String host) {
   this.username = username;
   this.password = password;
   this.rememberMe = rememberMe;
   this.host = host;
}

最终使用 Subjectlogin() 方法完成登录:void login(AuthenticationToken token) throws AuthenticationException,如果不成功,就会抛出一个 AuthenticationException 认证异常类的实例,shiro 提供了一些子类实现来标识认证失败的原因。

DelegatingSubjectlogin() 方法:

public void login(AuthenticationToken token) throws AuthenticationException {
	...
	// 由安全管理器执行登录操作
	// 如果登录成功,返回更新后的 Subject 对象,否则抛出异常
	// protected transient SecurityManager securityManager
	Subject subject = securityManager.login(this, token);

	// 做一些成员变量的赋值...
}

一般在配置类中配置 DefaultWebSecurityManager 的bean,继承关系:
在这里插入图片描述

直接继承了父类的 login() 方法:

  public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
      AuthenticationInfo info;
      try {
      	// private Authenticator authenticator 	
      	// 调用认证器的认证方法,如果成功,就返回AuthenticationInfo对象,否则抛出异常
          info = authenticate(token);
      } catch (AuthenticationException ae) {
          try {
          // 登录失败的逻辑,可以在子类重写
              onFailedLogin(token, ae, subject);
          } catch (Exception e) {
              if (log.isInfoEnabled()) {
                  log.info("onFailedLogin method threw an " +
                          "exception.  Logging and propagating original AuthenticationException.", e);
              }
          }
          throw ae; //propagate
      }
      // 登录成功,创建一个新的 Subject 对象返回
      Subject loggedIn = createSubject(token, info, subject);

      onSuccessfulLogin(token, info, loggedIn);

      return loggedIn;
  }

大致流程:
在这里插入图片描述

认证器如何完成认证操作 ?

安全管理器 DefaultWebSecurityManager 的父类 AuthenticatingSecurityManager 的构造器中:

private Authenticator authenticator;
public AuthenticatingSecurityManager() {
    super();
    // 所以,DefaultWebSecurityManager中默认使用的实现为ModularRealmAuthenticator
    this.authenticator = new ModularRealmAuthenticator();
}

在这里插入图片描述

AbstractAuthenticator 的认证方法:

// final,子类不能再重写
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
	...
	AuthenticationInfo info;
	info = doAuthenticate(token);
	...
	return info;
}
// 具体的认证逻辑
protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token) throws AuthenticationException;

这个类使用 final 标记了 authenticate() 方法,子类不能再重写,在 doAuthenticate() 执行前后做了一些校验、异常处理的操作,然后提供抽象方法 doAuthenticate()给子类,去实现真正的认证逻辑。

ModularRealmAuthenticator 实现的 doAuthenticate() 方法:

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
    assertRealmsConfigured();
    // private Collection<Realm> realms
    // 最终使用Realm与数据库打交道
    Collection<Realm> realms = getRealms();
    if (realms.size() == 1) {
    	// 一般自定义一个AuthorizingRealm的子类
    	// 放入IOC容器
    	// 核心方法:
        // AuthenticationInfo info = realm.getAuthenticationInfo(token)
        return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
    } else {
        return doMultiRealmAuthentication(realms, authenticationToken);
    }
}

AuthenticatingRealm 中实现了 getAuthenticationInfo() 方法:同样是使用 final 标记,然后提供一个抽象方法 :

protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

可以自定义一个 ShiroRealm 如:

public class ShiroRealm extends AuthorizingRealm {
	...
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取用户输入的用户名和密码
        String username = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());

        // 通过用户名到数据库查询用户信息
        User user = this.userService.findByName(username);

        if (user == null || !StringUtils.equals(password, user.getPassword())) {
            throw new IncorrectCredentialsException("用户名或密码错误!");
        }
        if (User.STATUS_LOCK.equals(user.getStatus())) {
            throw new LockedAccountException("账号已被锁定,请联系管理员!");
        }
        String deptIds = this.userDataPermissionService.findByUserId(String.valueOf(user.getUserId()));
        user.setDeptIds(deptIds);
        return new SimpleAuthenticationInfo(user, password, getName());
    }
	...
}

大致流程:

在这里插入图片描述

自定义的 Realm 实现建议继承 AuthorizingRealm,集成认证和授权两个模块。

Shiro 的过滤器

在这里插入图片描述

通过 AbstractShiroFilter 可以实现对不同请求的过滤,如:

// ShiroConfig.java
// ShiroFilterFactoryBean 用于创建 SpringShiroFilter
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
    // <1> 创建 ShiroFilterFactoryBean 对象,用于创建 ShiroFilter 过滤器
    ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

    // <2> 设置 SecurityManager
    filterFactoryBean.setSecurityManager(this.securityManager());

    // <3> 设置 URL 们
    filterFactoryBean.setLoginUrl("/login"); // 登录 URL
    filterFactoryBean.setSuccessUrl("/login_success"); // 登录成功 URL
    filterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 无权限 URL

    // <4> 设置 URL 的权限配置
    filterFactoryBean.setFilterChainDefinitionMap(this.filterChainDefinitionMap());

    return filterFactoryBean;
}
private Map<String, String> filterChainDefinitionMap() {
    Map<String, String> filterMap = new LinkedHashMap<>(); // 注意要使用有序的 LinkedHashMap ,顺序匹配
    filterMap.put("/test/echo", "anon"); // 允许匿名访问
    filterMap.put("/test/admin", "roles[ADMIN]"); // 需要 ADMIN 角色
    filterMap.put("/test/normal", "roles[NORMAL]"); // 需要 NORMAL 角色
    filterMap.put("/logout", "logout"); // 退出
    filterMap.put("/**", "authc"); // 默认剩余的 URL ,需要经过认证
    return filterMap;
}

<3>:

  • GET /login 为登录页面,POST /login 为登录请求
  • /login_success:登录成功后重定向
  • /unauthorized:没有权限时重定向

Shiro 提供了提供了一些实用的过滤器,在枚举类 DefaultFilter 里面可见:

public enum 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),
    invalidRequest(InvalidRequestFilter.class);

    private final Class<? extends Filter> filterClass;
    ... 
}

匿名过滤器:直接返回true,即可以匿名访问

@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
    // Always return true since we allow access to anyone
    return true;
}

认证过滤器FormAuthenticationFilter

  • 经过认证的用户可以直接访问。
  • 匿名用户,但请求的是loginUrl(在创建 ShiroFilterFactoryBean 的时候设置,默认为 login.jsp)时,如果是 GET 请求,就跳转到登录页,如果为 POST,就会该用户进行认证。
  • 匿名用户,请求的不是 loginUrl,就先登录,登录成功后再重定向到该 URL。

FormAuthenticationFilter 的父类 AccessControlFilter中:

public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

1、先判断是否可以访问 isAccessAllowed(),核心逻辑:

// AuthenticationFilter.java
subject.isAuthenticated() && subject.getPrincipal() != null

或者:

(!isLoginRequest(request, response) && isPermissive(mappedValue))

即判断当前用户是否认证过,或者拥有访问该 URL 的权限。
如果满足条件,直接返回true。

2、如果是匿名用户,执行 onAccessDenied()

// FormAuthenticationFilter.java
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    if (isLoginRequest(request, response)) { // 是登录请求
    	// POST 请求, 执行登录逻辑
        if (isLoginSubmission(request, response)) {
			...
            return executeLogin(request, response);
        } else {
            ....
            //allow them to see the login page ;)
            // 要去登录页,直接放行
            return true;
        }
    } else { // 不是登录请求
        ...
        // 保存这个请求并且重定向到登录页
        // 保存:实际上就是放入当前Subject的Session中,key为shiroSavedRequest
        saveRequestAndRedirectToLogin(request, response);
        return false;
    }
}

流程:
在这里插入图片描述

角色过滤器RolesAuthorizationFilter

在这里插入图片描述

public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

    Subject subject = getSubject(request, response);
    // 哪些角色可以访问这个URL
    String[] rolesArray = (String[]) mappedValue;

    if (rolesArray == null || rolesArray.length == 0) {
        //no roles specified, so nothing to check - allow access.
        return true;
    }

    Set<String> roles = CollectionUtils.asSet(rolesArray);
    return subject.hasAllRoles(roles);
}

权限过滤器PermissionsAuthorizationFilter,拥有指定权限的用户才可以访问。

public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

    Subject subject = getSubject(request, response);
    // 访问URL所需的权限
    String[] perms = (String[]) mappedValue;
    boolean isPermitted = true;
    if (perms != null && perms.length > 0) {
        if (perms.length == 1) {
            if (!subject.isPermitted(perms[0])) {
                isPermitted = false;
            }
        } else {
            if (!subject.isPermittedAll(perms)) {
                isPermitted = false;
            }
        }
    }
    return isPermitted;
}

Shiro 访问控制过滤器:AccessControlFilter

AccessControlFilterShiro 的核心过滤器,定义了判断控制访问的逻辑:

public abstract class AccessControlFilter extends PathMatchingFilter {
	public static final String DEFAULT_LOGIN_URL = "/login.jsp";
	public static final String GET_METHOD = "GET";
	public static final String POST_METHOD = "POST";
	private String loginUrl = DEFAULT_LOGIN_URL;

    // 判断是否可以访问
	protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;
	// 访问被拒绝后的行为,如认证过滤器就是跳到登录页
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return onAccessDenied(request, response);
    }
    protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;

    // 过滤请求
    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
       return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
   }
   ...
}

权限过滤器、角色过滤器等过滤器都是在重写 isAccessAllowed()onAccessDenied() 方法,从而实现认证和授权。

AccessControlFilter 的创建实现:主要分为认证和授权两个模块

在这里插入图片描述

Shiro 注解

Shiro 提供了几个注解,可以直接标记在 Spring Web MVCcontroller 的方法上,如:

@PostMapping("/dept")
// 表示 POST /dept 请求需要对 dept模块的add权限
// 相当于给这个URL配置了一个 PermissionsAuthorizationFilter
@RequiresPermissions("dept:add")  
public Result addDept(@Valid Dept dept) {
    deptService.createDept(dept);
    return new Result().success();
}

作用相当于为该URL添加过滤器。

  • @RequiresGuest 相当于添加 AnonymousFilter
  • @RequiresAuthentication 相当于添加 FormAuthenticationFilter
  • @RequiresUser 相当于添加 UserFilter
  • @RequiresRoles 相当于添加 RolesAuthorizationFilter
  • @RequiresPermissions 相当于添加 PermissionsAuthorizationFilter

Shiro 认证异常

账号异常:
在这里插入图片描述

密码异常:

在这里插入图片描述

还有一个:

public class UnsupportedTokenException extends AuthenticationException {
	...
}

在认证的过程中,没有一个配置好的 Realm 支持当前 AuthenticationToken实例,就会抛出这个异常。

Shiro 授权异常

在这里插入图片描述

扫描 shiro 注解的组件是什么 ?

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值