Shiro笔记

本文详细介绍了Shiro框架在权限验证中的应用,包括身份认证、授权、会话管理和缓存管理等核心功能。通过讲解Shiro的工作流程、Realm配置、会话管理以及与Web的集成,展示了如何在单体应用中实现权限管理。此外,还涉及了Shiro与Spring、Spring Boot的整合,以及如何实现单点登录功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

权限验证——Shiro

标签(空格分隔): 权限验证 springcloud springsecurity shiro gateway


Shiro单体应用权限管理

简介

Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

  • Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。

Shiro工作流程

applicationCode ----> Subject ------> SecurityManager ------> Realm

Subject : 对外主体,代表了当前的用户。与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

其他组件:

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

SessionManager:如果写过Servlet就应该知道Session的概念,Session需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所以呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);

SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。

身份验证

在shiro中,用户需要提供principals(身份)和 credentials(证明)。两者都在Subject中。

//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager  
    Factory<org.apache.shiro.mgt.SecurityManager> factory =  
            new IniSecurityManagerFactory("classpath:shiro.ini");  
    //2、得到SecurityManager实例 并绑定给SecurityUtils  
    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();  
    SecurityUtils.setSecurityManager(securityManager);  
    //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)  
    Subject subject = SecurityUtils.getSubject();  
    UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");  
  
    try {  
        //4、登录,即身份验证  
        //自动委托给SecurityManager.login方法进行登录
        subject.login(token);  
    } catch (AuthenticationException e) {  
        //5、身份验证失败  
    }  
  
    Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录  
  
    //6、退出  
    subject.logout();  

身份验证流程

  1. subject.login()委托给securityManager处理
  2. securityManager不负责真正的验证逻辑,他会将验证委托给Authenticator
  3. Authenticator会委托给相应的AuthenticationStrategy来进行多Realm验证
  4. Authenticator会将token传入Realm,从Realm中获取身份验证信息。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

Realm域:
可以把Realm看成DataSource,即安全数据源,从Realm中可以判断用户的身份信息及权限是否满足要求。

org.apache.shiro.realm.Realm接口如下:

String getName(); //返回一个唯一的Realm名字  
boolean supports(AuthenticationToken token); //判断此Realm是否支持此Token  
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)  
 throws AuthenticationException;  //根据Token获取认证信息  

单Realm配置

  1. 自定义Realm
public class MyRealm1 implements Realm {  
    @Override  
    public String getName() {  
        return "myrealm1";  
    }  
    @Override  
    public boolean supports(AuthenticationToken token) {  
        //仅支持UsernamePasswordToken类型的Token  
        return token instanceof UsernamePasswordToken;   
    }  
    @Override  
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
        String username = (String)token.getPrincipal();  //得到用户名  
        String password = new String((char[])token.getCredentials()); //得到密码  
        if(!"zhang".equals(username)) {  
            throw new UnknownAccountException(); //如果用户名错误  
        }  
        if(!"123".equals(password)) {  
            throw new IncorrectCredentialsException(); //如果密码错误  
        }  
        //如果身份认证验证成功,返回一个AuthenticationInfo实现;  
        return new SimpleAuthenticationInfo(username, password, getName());  
    }  
}   
  1. 、ini配置文件指定自定义Realm实现(shiro-realm.ini)
#声明一个realm
myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1
#指定securityManager的realms实现
#通过$name来引入之前的realm定义
securityManager.realms=$myRealm1
  1. 测试
public void testCustomRealm() {
    //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
    Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro-realm.ini");

    //2、得到SecurityManager实例 并绑定给SecurityUtils
    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);

    //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

    try {
        //4、登录,即身份验证
        subject.login(token);
    } catch (AuthenticationException e) {
        //5、身份验证失败
        e.printStackTrace();
    }

    Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录

    //6、退出
    subject.logout();
}

多Realm配置***

  1. ini配置文件
#声明一个realm  
myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1  
myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2  
#指定securityManager的realms实现  
securityManager.realms=$myRealm1,$myRealm2   

securityManager会按照realms指定的顺序进行身份认证。若没有securityManager.realms,那么seurityManager会按照realm的声明顺序进行使用。

    //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
    Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro-multi-realm.ini");

    //2、得到SecurityManager实例 并绑定给SecurityUtils
    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);

    //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("wang", "123");

    try {
        //4、登录,即身份验证
        subject.login(token);
    } catch (AuthenticationException e) {
        //5、身份验证失败
        e.printStackTrace();
    }

    Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录

    //6、退出
    subject.logout();

Shiro默认提供的Realm

一般继承AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。

JDBC Realm使用
  1. 添加maven依赖
<dependency>  
   <groupId>mysql</groupId>  
   <artifactId>mysql-connector-java</artifactId>  
   <version>5.1.25</version>  
</dependency>  
<dependency>  
   <groupId>com.alibaba</groupId>  
   <artifactId>druid</artifactId>  
   <version>0.2.23</version>  
</dependency>   
  1. ini配置
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm  
dataSource=com.alibaba.druid.pool.DruidDataSource  
dataSource.driverClassName=com.mysql.jdbc.Driver  
dataSource.url=jdbc:mysql://localhost:3306/shiro  
dataSource.username=root  
#dataSource.password=  
jdbcRealm.dataSource=$dataSource  
securityManager.realms=$jdbcRealm   
  1. 使用方式
public void testJDBCRealm() {
   //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
   Factory<org.apache.shiro.mgt.SecurityManager> factory =
           new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");

   //2、得到SecurityManager实例 并绑定给SecurityUtils
   org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
   SecurityUtils.setSecurityManager(securityManager);

   //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
   Subject subject = SecurityUtils.getSubject();
   UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

   try {
       //4、登录,即身份验证
       subject.login(token);
   } catch (AuthenticationException e) {
       //5、身份验证失败
       e.printStackTrace();
   }

   Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录

   //6、退出
   subject.logout();
}

Authenticator及AuthenticationStrategy

Authenticator的职责是验证用户帐号,是Shiro API中身份验证核心的入口点:

  • public AuthenticationInfo authenticate(AuthenticationToken authenticationToken) throws AuthenticationException;

验证规则通过AuthenticationStrategy接口指定,默认提供的实现:

FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;

AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;

AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。

自定义AuthenticationStrategy实现,首先看其API:

//在所有Realm验证之前调用  
AuthenticationInfo beforeAllAttempts(  
Collection<? extends Realm> realms, AuthenticationToken token)   
throws AuthenticationException;  
//在每个Realm之前调用  
AuthenticationInfo beforeAttempt(  
Realm realm, AuthenticationToken token, AuthenticationInfo aggregate)   
throws AuthenticationException;  
//在每个Realm之后调用  
AuthenticationInfo afterAttempt(  
Realm realm, AuthenticationToken token,   
AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)  
throws AuthenticationException;  
//在所有Realm之后调用  
AuthenticationInfo afterAllAttempts(  
AuthenticationToken token, AuthenticationInfo aggregate)   
throws AuthenticationException;   

因为每个AuthenticationStrategy实例都是无状态的,所有每次都通过接口将相应的认证信息传入下一次流程;通过如上接口可以进行如合并/返回第一个验证成功的认证信息。

自定义实现时一般继承org.apache.shiro.authc.pam.AbstractAuthenticationStrategy即可

public class OnlyOneAuthenticatorStrategy extends AbstractAuthenticationStrategy {

    @Override
    public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException {
        return new SimpleAuthenticationInfo();//返回一个权限的认证信息
    }

    @Override
    public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
        return aggregate;//返回之前合并的
    }

    @Override
    public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {
        AuthenticationInfo info;
        if (singleRealmInfo == null) {
            info = aggregateInfo;
        } else {
            if (aggregateInfo == null) {
                info = singleRealmInfo;
            } else {
                info = merge(singleRealmInfo, aggregateInfo);
                if(info.getPrincipals().getRealmNames().size() > 1) {
                    System.out.println(info.getPrincipals().getRealmNames());
                    throw new AuthenticationException("Authentication token of type [" + token.getClass() + "] " +
                            "could not be authenticated by any configured realms.  Please ensure that only one realm can " +
                            "authenticate these tokens.");
                }
            }
        }


        return info;
    }

    @Override
    public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
        return aggregate;
    }
}

授权

shiro 支持三种授权模式

编程式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {  
    //有权限  
} else {  
    //无权限  
}   
注解式
@RequiresRoles("admin")  
public void hello() {  
    //有权限  
}   
JSP/GSP标签:在JSP/GSP页面通过相应的标签完成: 
<shiro:hasRole name="admin">  
<!— 有权限 —>  
</shiro:hasRole> 

基于角色的访问控制(隐式角色)

配置ini文件

[users]
zhang=123, role1, role2
wang=123, role1

Shiro不负责维护用户-角色信息,需要应用提供,Shiro只是提供相应的接口方便验证

Shiro提供了hasRole/hasRole用于判断用户是否拥有某个角色/某些权限;但是没有提供如hashAnyRole用于判断是否有某些权限中的某一个。

//判断拥有角色:role1  
Assert.assertTrue(subject().hasRole("role1"));  
//判断拥有角色:role1 and role2  
Assert.assertTrue(subject().hasAllRoles(Arrays.asList("role1", "role2")));
//判断拥有角色:role1 and role2 and !role3  
boolean[] result = subject().hasRoles(Arrays.asList("role1", "role2", "role3")); 

Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles不同的地方是它在判断为假的情况下会抛出UnauthorizedException异常

//断言拥有角色:role1  
subject().checkRole("role1");  
//断言拥有角色:role1 and role3 失败抛出异常  
subject().checkRoles("role1", "role3");  

基于资源的访问控制(显示角色)

在ini配置文件配置用户拥有的角色及角色-权限关系

[users]
wang=123, role1, role2
zhang=123, role1
[roles]
role1=user:create,user:update
role2=user:create,user:delete

角色是权限集合;Shiro同样不进行权限的维护,需要我们通过Realm返回相应的权限信息。只需要维护“用户——角色”之间的关系即可。

Subject

  1. 身份信息获取
Object getPrincipal(); //Primary Principal  
PrincipalCollection getPrincipals(); // PrincipalCollection   
  1. 身份验证
void login(AuthenticationToken token) throws AuthenticationException; 
//登陆成功,返回值为true
boolean isAuthenticated();  
//通过rememberMe登陆返回true,与isAuthenticated互斥,结果相反
boolean isRemembered();  
  1. 角色授权验证
boolean hasRole(String roleIdentifier);  
boolean[] hasRoles(List<String> roleIdentifiers);  
boolean hasAllRoles(Collection<String> roleIdentifiers);  
void checkRole(String roleIdentifier) throws AuthorizationException;  
void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;  
void checkRoles(String... roleIdentifiers) throws AuthorizationException;   
  1. 权限验证:
Subject类
boolean isPermitted(String permission);  
boolean isPermitted(Permission permission);  
boolean[] isPermitted(String... permissions);  
boolean[] isPermitted(List<Permission> permissions);  
boolean isPermittedAll(String... permissions);  
boolean isPermittedAll(Collection<Permission> permissions);  
void checkPermission(String permission) throws AuthorizationException;  
void checkPermission(Permission permission) throws AuthorizationException;  
void checkPermissions(String... permissions) throws AuthorizationException;  
void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;  

多线程

<V> V execute(Callable<V> callable) throws ExecutionException;  
void execute(Runnable runnable);  
<V> Callable<V> associateWith(Callable<V> callable);  
Runnable associateWith(Runnable runnable);   

Subject是与线程绑定的。因此在多线程执行中需要传播到相应的线程才能获取到相应的Subject。最简单的办法就是通过execute(runnable/callable实例)直接调用;或者通过associateWith(runnable/callable实例)得到一个包装后的实例;它们都是通过:1、把当前线程的Subject绑定过去;2、在线程执行结束后自动释放。

与WEB集成

通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制,ShiroFilter类似于如Strut2/SpringMVC这种web框架的前端控制器,其是安全控制的入口点,其负责读取配置(如ini配置文件),然后判断URL是否需要登录/权限等工作。

拦截器

  1. NameableFilter
    NameableFilter给Filter起个名字,如果没有设置默认就是FilterName;还记得之前的如authc吗?当我们组装拦截器链时会根据这个名字找到相应的拦截器实例;

  2. OncePerRequestFilter
    OncePerRequestFilter用于防止多次执行Filter的;也就是说一次请求只会走一次拦截器链;另外提供enabled属性,表示是否开启该拦截器实例,默认enabled=true表示开启,如果不想让某个拦截器工作,可以设置为false即可。

  3. ShiroFilter
    ShiroFilter是整个Shiro的入口点,用于拦截需要安全控制的请求进行处理,这个之前已经用过了。

  4. AdviceFilter
    AdviceFilter提供了AOP风格的支持,类似于SpringMVC中的Interceptor:

boolean preHandle(ServletRequest request, ServletResponse response) throws Exception  
void postHandle(ServletRequest request, ServletResponse response) throws Exception  
void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception; 
  1. PathMatchingFilter
    PathMatchingFilter提供了基于Ant风格的请求路径匹配功能及拦截器参数解析的功能,如“roles[admin,user]”自动根据“,”分割解析到一个路径参数配置并绑定到相应的路径。

  2. AccessControlFilter
    AccessControlFilter提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等:

abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  
boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  
abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;   

isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部,如果允许访问返回true,否则false;

onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。

onPreHandle会自动调用这两个方法决定是否继续处理:

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

另外AccessControlFilter还提供了如下方法用于处理如登录成功后/重定向到上一个请求:

void setLoginUrl(String loginUrl) //身份验证时使用,默认/login.jsp  
String getLoginUrl()  
Subject getSubject(ServletRequest request, ServletResponse response) //获取Subject实例  
boolean isLoginRequest(ServletRequest request, ServletResponse response)//当前请求是否是登录请求  
void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //将当前请求保存起来并重定向到登录页面  
void saveRequest(ServletRequest request) //将请求保存起来,如登录成功后再重定向回该请求  
void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登录页面 

拦截器链

Shiro的ProxiedFilterChain执行流程:1、先执行Shiro自己的Filter链;2、再执行Servlet容器的Filter链(即原始的Filter)。

Shiro内部提供了一个路径匹配的FilterChainResolver实现:PathMatchingFilterChainResolver,其根据[urls]中配置的url模式(默认Ant风格)=拦截器链和请求的url是否匹配来解析得到配置的拦截器链的;而PathMatchingFilterChainResolver内部通过FilterChainManager维护着拦截器链,比如DefaultFilterChainManager实现维护着url模式与拦截器链的关系。因此我们可以通过FilterChainManager进行动态动态增加url模式与拦截器链的关系。

自定义FilterChainResolver,可以通过实现WebEnvironment接口完成。如果想动态实现url-拦截器的注册,就可以通过实现此处的FilterChainResolver来完成,比如:

public class MyIniWebEnvironment extends IniWebEnvironment {  
    @Override  
    protected FilterChainResolver createFilterChainResolver() {  
        //在此处扩展自己的FilterChainResolver  
        //1、创建FilterChainResolver  
    PathMatchingFilterChainResolver filterChainResolver =  
            new PathMatchingFilterChainResolver();  
    //2、创建FilterChainManager  
    DefaultFilterChainManager filterChainManager = new DefaultFilterChainManager();  
    //3、注册Filter  
    for(DefaultFilter filter : DefaultFilter.values()) {  
        filterChainManager.addFilter(  
            filter.name(), (Filter) ClassUtils.newInstance(filter.getFilterClass()));  
    }  
    //4、注册URL-Filter的映射关系  
    filterChainManager.addToChain("/login.jsp", "authc");  
    filterChainManager.addToChain("/unauthorized.jsp", "anon");  
    filterChainManager.addToChain("/**", "authc");  
    filterChainManager.addToChain("/**", "roles", "admin");  
      
    //5、设置Filter的属性  
    FormAuthenticationFilter authcFilter =  
             (FormAuthenticationFilter)filterChainManager.getFilter("authc");  
    authcFilter.setLoginUrl("/login.jsp");  
    RolesAuthorizationFilter rolesFilter =  
              (RolesAuthorizationFilter)filterChainManager.getFilter("roles");  
    rolesFilter.setUnauthorizedUrl("/unauthorized.jsp");  
      
    filterChainResolver.setFilterChainManager(filterChainManager);  
    return filterChainResolver;   
    }  
} 

JSP标签

导入标签库

<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>  
  1. guest标签
<shiro:guest>  
欢迎游客访问,<a href="${pageContext.request.contextPath}/login.jsp">登录</a>  
</shiro:guest>   

用户没有身份验证时显示相应信息,即游客访问信息。

  1. user标签
<shiro:user>  
欢迎[<shiro:principal/>]登录,<a href="${pageContext.request.contextPath}/logout">退出</a>  
</shiro:user>   

用户已经身份验证/记住我登录后显示相应的信息。

  1. authenticated标签
<shiro:authenticated>  
   用户[<shiro:principal/>]已身份验证通过  
</shiro:authenticated>   

用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的。

  1. notAuthenticated标签
<shiro:notAuthenticated>
   未身份验证(包括记住我)
</shiro:notAuthenticated> 

用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。

  1. principal标签
<shiro: principal/>

显示用户身份信息,默认调用Subject.getPrincipal()获取,即Primary Principal。

#相当于Subject.getPrincipals().oneByType(String.class)。
<shiro:principal type="java.lang.String"/>  
#相当于((User)Subject.getPrincipals()).getUsername()。
<shiro:principal property="username"/>  
  1. hasRole标签
<shiro:hasRole name="admin">  
   用户[<shiro:principal/>]拥有角色admin<br/>  
</shiro:hasRole>  
  1. hasAnyRoles标签
<shiro:hasAnyRoles name="admin,user">  
   用户[<shiro:principal/>]拥有角色admin或user<br/>  
</shiro:hasAnyRoles> 
  1. lacksRole标签
<shiro:lacksRole name="abc">  
   用户[<shiro:principal/>]没有角色abc<br/>  
</shiro:lacksRole> 

如果当前Subject没有角色将显示body体内容。

  1. hasPermission标签
<shiro:hasPermission name="user:create">  
   用户[<shiro:principal/>]拥有权限user:create<br/>  
</shiro:hasPermission>   
  1. lacksPermission标签
<shiro:lacksPermission name="org:create">  
   用户[<shiro:principal/>]没有权限org:create<br/>  
</shiro:lacksPermission> 

会话管理

所谓会话,即用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。

login("classpath:shiro.ini", "zhang", "123");  
Subject subject = SecurityUtils.getSubject();  
Session session = subject.getSession();   

登陆成功后调用subject.getSession会返回一个Session,若不存在会重新创建,若getSession(false)时,不存在会返回null。

//获取会话唯一标识
session.getId();

//获取当前subject的主机地址
session.getHost();

//获取/设置当前Session的过期时间;如果不设置默认是会话管理器的全局过期时间。
session.getTimeout();  
session.setTimeout(毫秒); 


//获取会话的启动时间及最后访问时间;如果是JavaSE应用需要自己定期调用session.touch()去更新最后访问时间;如果是Web应用,每次进入ShiroFilter都会自动调用session.touch()来更新最后访问时间。 
session.getStartTimestamp();  
session.getLastAccessTime();  

//更新会话最后访问时间及销毁会话;当Subject.logout()时会自动调用stop方法来销毁会话。如果在web中,调用javax.servlet.http.HttpSession.invalidate()也会自动调用Shiro Session.stop方法进行销毁Shiro的会话。 
session.touch();  
session.stop();   

//不依赖于tomcat这样的容器,在SE、EE中都可以使用
session.setAttribute("key", "123");  
Assert.assertEquals("123", session.getAttribute("key"));  
session.removeAttribute("key");  

会话管理器

会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。
SecurityManager提供了如下接口:

Session start(SessionContext context); //启动会话  
Session getSession(SessionKey key) throws SessionException; //根据会话Key获取会话  

另外用于Web环境的WebSessionManager又提供了如下接口:

boolean isServletContainerSessions();//是否使用Servlet容器的会话  

Shiro提供了三个默认实现:

DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境;

ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;

DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。

替换SecurityManager可以在ini中配置

[main]  
sessionManager=org.apache.shiro.web.session.mgt.ServletContainerSessionManager
securityManager.sessionManager=$sessionManager 

如果使用ServletContainerSessionManager进行会话管理,Session的超时依赖于底层Servlet容器的超时时间,可以在web.xml中配置其会话的超时时间(分钟为单位):

<session-config>  
  <session-timeout>30</session-timeout>  
</session-config> 

会话监听器

会话监听器用于监听会话创建、过期及停止事件:

public class MySessionListener1 implements SessionListener {  
    @Override  
    public void onStart(Session session) {//会话创建时触发  
        System.out.println("会话创建:" + session.getId());  
    }  
    @Override  
    public void onExpiration(Session session) {//会话过期时触发  
        System.out.println("会话过期:" + session.getId());  
    }  
    @Override  
    public void onStop(Session session) {//退出/会话过期时触发  
        System.out.println("会话停止:" + session.getId());  
    }    
}  

如果只想监听某一个事件,可以继承SessionListenerAdapter实现:

public class MySessionListener2 extends SessionListenerAdapter {
    @Override
    public void onStart(Session session) {
        System.out.println("会话创建:" + session.getId());
    }
}

在shiro-web.ini配置文件中可以进行如下配置设置会话监听器:

sessionListener1=listener.MySessionListener1  
sessionListener2=listener.MySessionListener2  
sessionManager.sessionListeners=$sessionListener1,$sessionListener2  

会话验证

Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话。主要用于在web端用户长时间登陆之后,不知道会话是否过期。

在ini配置调度器

sessionValidationScheduler=org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler  
sessionValidationScheduler.interval = 3600000  
sessionValidationScheduler.sessionManager=$sessionManager  
sessionManager.globalSessionTimeout=1800000  
sessionManager.sessionValidationSchedulerEnabled=true  
sessionManager.sessionValidationScheduler=$sessionValidationScheduler 

sessionValidationScheduler:会话验证调度器,sessionManager默认就是使用ExecutorServiceSessionValidationScheduler,其使用JDK的ScheduledExecutorService进行定期调度并验证会话是否过期;

Shiro也提供了使用Quartz会话验证调度器:

sessionValidationScheduler=org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler  
sessionValidationScheduler.sessionValidationInterval = 3600000  
sessionValidationScheduler.sessionManager=$sessionManager   

使用时需要导入shiro-quartz依赖:

<dependency>  
     <groupId>org.apache.shiro</groupId>  
     <artifactId>shiro-quartz</artifactId>  
     <version>1.2.2</version>  
</dependency>  

缓存管理

ini配置

userRealm=realm.UserRealm  
userRealm.credentialsMatcher=$credentialsMatcher  
userRealm.cachingEnabled=true  
userRealm.authenticationCachingEnabled=true  
userRealm.authenticationCacheName=authenticationCache  
userRealm.authorizationCachingEnabled=true  
userRealm.authorizationCacheName=authorizationCache  
securityManager.realms=$userRealm  
  
cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager  
cacheManager.cacheManagerConfigFile=classpath:shiro-ehcache.xml  
securityManager.cacheManager=$cacheManager   

userRealm.cachingEnabled:启用缓存,默认false;

userRealm.authenticationCachingEnabled:启用身份验证缓存,即缓存AuthenticationInfo信息,默认false;

userRealm.authenticationCacheName:缓存AuthenticationInfo信息的缓存名称;

userRealm. authorizationCachingEnabled:启用授权缓存,即缓存AuthorizationInfo信息,默认false;

userRealm. authorizationCacheName:缓存AuthorizationInfo信息的缓存名称;

cacheManager:缓存管理器

login(u1.getUsername(), password);  
//修改密码
userService.changePassword(u1.getId(), password + "1");  

RealmSecurityManager securityManager =  
 (RealmSecurityManager) SecurityUtils.getSecurityManager();  
UserRealm userRealm = (UserRealm) securityManager.getRealms().iterator().next();  
//清除缓存
userRealm.clearCachedAuthenticationInfo(subject().getPrincipals());  

login(u1.getUsername(), password + "1");  

Spring集成

JavaSE应用

<!-- 缓存管理器 使用Ehcache实现 -->  
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">  
    <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>  
</bean>  
  
<!-- 凭证匹配器 -->  
<bean id="credentialsMatcher" class="  
com.github.zhangkaitao.shiro.chapter12.credentials.RetryLimitHashedCredentialsMatcher">  
    <constructor-arg ref="cacheManager"/>  
    <property name="hashAlgorithmName" value="md5"/>  
    <property name="hashIterations" value="2"/>  
    <property name="storedCredentialsHexEncoded" value="true"/>  
</bean>  
  
<!-- Realm实现 -->  
<bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter12.realm.UserRealm">  
    <property name="userService" ref="userService"/>  
    <property name="credentialsMatcher" ref="credentialsMatcher"/>  
    <property name="cachingEnabled" value="true"/>  
    <property name="authenticationCachingEnabled" value="true"/>  
    <property name="authenticationCacheName" value="authenticationCache"/>  
    <property name="authorizationCachingEnabled" value="true"/>  
    <property name="authorizationCacheName" value="authorizationCache"/>  
</bean>  
<!-- 会话ID生成器 -->  
<bean id="sessionIdGenerator"   
class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>  
<!-- 会话DAO -->  
<bean id="sessionDAO"   
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">  
    <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>  
    <property name="sessionIdGenerator" ref="sessionIdGenerator"/>  
</bean>  
<!-- 会话验证调度器 -->  
<bean id="sessionValidationScheduler"   
class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">  
    <property name="sessionValidationInterval" value="1800000"/>  
    <property name="sessionManager" ref="sessionManager"/>  
</bean>  
<!-- 会话管理器 -->  
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">  
    <property name="globalSessionTimeout" value="1800000"/>  
    <property name="deleteInvalidSessions" value="true"/>  
    <property name="sessionValidationSchedulerEnabled" value="true"/>  
   <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>  
    <property name="sessionDAO" ref="sessionDAO"/>  
</bean>  
<!-- 安全管理器 -->  
<bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">  
    <property name="realms">  
        <list><ref bean="userRealm"/></list>  
    </property>  
    <property name="sessionManager" ref="sessionManager"/>  
    <property name="cacheManager" ref="cacheManager"/>  
</bean>  
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->  
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">  
<property name="staticMethod"   
value="org.apache.shiro.SecurityUtils.setSecurityManager"/>  
    <property name="arguments" ref="securityManager"/>  
</bean>  
<!-- Shiro生命周期处理器-->  
<bean id="lifecycleBeanPostProcessor"   
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  

WEB应用在配置上不同之处如下

<!-- 会话Cookie模板 -->  
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">  
    <constructor-arg value="sid"/>  
    <property name="httpOnly" value="true"/>  
    <property name="maxAge" value="180000"/>  
</bean>  
<!-- 会话管理器 -->  
<bean id="sessionManager"   
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">  
    <property name="globalSessionTimeout" value="1800000"/>  
    <property name="deleteInvalidSessions" value="true"/>  
    <property name="sessionValidationSchedulerEnabled" value="true"/>  
    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>  
    <property name="sessionDAO" ref="sessionDAO"/>  
    <property name="sessionIdCookieEnabled" value="true"/>  
    <property name="sessionIdCookie" ref="sessionIdCookie"/>  
</bean>  
<!-- 安全管理器 -->  
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
<property name="realm" ref="userRealm"/>  
    <property name="sessionManager" ref="sessionManager"/>  
    <property name="cacheManager" ref="cacheManager"/>  
</bean>  

<!-- 基于Form表单的身份验证过滤器 -->
<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
    <property name="usernameParam" value="username"/>
    <property name="passwordParam" value="password"/>
    <property name="loginUrl" value="/login.jsp"/>
</bean>

<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/login.jsp"/>
    <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
    <property name="filters">
        <util:map>
            <entry key="authc" value-ref="formAuthenticationFilter"/>
        </util:map>
    </property>
    <property name="filterChainDefinitions">
        <value>
            /index.jsp = anon
            /unauthorized.jsp = anon
            /login.jsp = authc
            /logout = logout
            /** = user
        </value>
    </property>
</bean>

配置web.xml

<context-param>  
    <param-name>contextConfigLocation</param-name>  
    <param-value>  
        classpath:spring-beans.xml,  
        classpath:spring-shiro-web.xml  
    </param-value>  
</context-param>  
<listener>  
   <listener-class>  
org.springframework.web.context.ContextLoaderListener  
</listener-class>  
</listener>   
<filter>  
    <filter-name>shiroFilter</filter-name>  
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
    <init-param>  
        <param-name>targetFilterLifecycle</param-name>  
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping> 

Spring Boot2.0 整合 Shiro + JWT

  1. 创建用户、角色、权限实体类、Dao及Service,用于用户身份及权限信息的获取。

  2. 自定义Realm

    public class ShiroRealm extends AuthorizingRealm {
   
       private static final Logger logger = LoggerFactory.getLogger(ShiroRealm.class);
   
       @Autowired
       private UserMapper userMapper;
       @Autowired
       private UserRoleMapper userRoleMapper;
       @Autowired
       private RolePermissionMapper rolePermissionMapper;
   
       @Override
       public boolean supports(AuthenticationToken token) {
           return token instanceof JWTToken;
       }
   
       /**
        * 获取用户角色和权限
        */
       @Override
       protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) {
           String username = JWTUtil.getUsername(token.toString());
   
           logger.info("用户"+username+"获取用户权限----ShiroRealm.doGetAuthorizationInfo");
           SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
   
           //获取用户角色集
           List<Role> roles = userRoleMapper.findByUserName(username);
           Set<String> roleSet = new HashSet<>();
           for (Role r: roles){
               roleSet.add(r.getName());
           }
           authorizationInfo.setRoles(roleSet);
   
           //获取用户权限集
           List<Permission> permissions = rolePermissionMapper.findByUserName(username);
           Set<String> permissionSet = new HashSet<>();
           for (Permission permission: permissions){
               permissionSet.add(permission.getName());
           }
           authorizationInfo.setStringPermissions(permissionSet);
           return authorizationInfo;
       }
   
       @Override
       protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
           // 这里的 token是从 JWTFilter 的 executeLogin方法传递过来的,已经经过了解密
           String token = (String) authenticationToken.getCredentials();
           String username = JWTUtil.getUsername(token);
   
           logger.info("用户"+username+"认证——ShiroRealm.doGetAuthenticationInfo");
   
           if (StringUtils.isBlank(username))
               throw new AuthenticationException("token校验不通过");
   
           //查询用户
           User user = userMapper.findByUserName(username);
   
           if (user == null){
               throw new UnknownAccountException("用户名密码错误!");
           }
           if (!JWTUtil.verify(token, username, user.getPasswd()))
               throw new AuthenticationException("token校验不通过");
           if (user.getStatus() == 1){
               throw new LockedAccountException("账号已被冻结,请联系管理员!");
           }
           SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(token,token,getName());
           return info;
       }
   
   }
  1. 创建JWT过滤器,实现身份认证和权限验证。此版本是通过BasicHttp
public class JWTFilter extends BasicHttpAuthenticationFilter {

   private static Logger logger = LoggerFactory.getLogger(JWTFilter.class);

   private static final String TOKEN = "Token";

   private AntPathMatcher pathMatcher = new AntPathMatcher();

   /**
    * 表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分
    * @param request
    * @param response
    * @param mappedValue
    * @return
    * @throws UnauthorizedException
    */
   @Override
   protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
       HttpServletRequest httpServletRequest = (HttpServletRequest) request;
       SystemProperties properties = SpringContextUtil.getBean(SystemProperties.class);
       String[] anonUrl = StringUtils.splitByWholeSeparatorPreserveAllTokens(properties.getAnonUrl(), ",");

       if (anonUrl == null) return false;
       boolean match = false;
       for (String u : anonUrl) {
           if (pathMatcher.match(u, httpServletRequest.getRequestURI()))
               match = true;
       }
       if (match) return true;
       if (isLoginAttempt(request, response)) {
           return executeLogin(request, response);
       }
       return false;
   }

   /**
    * 判断用户是否想要登入。
    * 检测header里面是否包含Authorization字段即可
    */
   @Override
   protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
       HttpServletRequest req = (HttpServletRequest) request;
       String token = req.getHeader(TOKEN);
       return token != null;
   }

   @Override
   protected boolean executeLogin(ServletRequest request, ServletResponse response) {
       HttpServletRequest httpServletRequest = (HttpServletRequest) request;
       String token = httpServletRequest.getHeader(TOKEN);
       JWTToken jwtToken = new JWTToken(token);
       try {
           getSubject(request, response).login(jwtToken);
           return true;
       } catch (Exception e) {
           logger.error(e.getMessage());
           return false;
       }
   }

   /**
    * 对跨域提供支持
    */
   @Override
   protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
       HttpServletRequest httpServletRequest = (HttpServletRequest) request;
       HttpServletResponse httpServletResponse = (HttpServletResponse) response;
       httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
       httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
       httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
       // 跨域时会首先发送一个 option请求,这里我们给 option请求直接返回正常状态
       if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
           httpServletResponse.setStatus(HttpStatus.OK.value());
           return false;
       }
       return super.preHandle(request, response);
   }
}
  1. Config配置类
@Configuration
public class ShiroConfig {

   @Bean
   public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){

       
       ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
       // 设置 securityManager
       shiroFilterFactoryBean.setSecurityManager(securityManager);

       // 在 Shiro过滤器链上加入 JWTFilter
       LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();
       filters.put("jwt", new JWTFilter());
       shiroFilterFactoryBean.setFilters(filters);

       //过滤链地址对应的Filter过滤器的注册
       LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();


       //登陆的url
       shiroFilterFactoryBean.setLoginUrl("/login");
       // 定义filterChain,静态资源不拦截
       filterChainDefinitionMap.put("/css/**","anon");
       filterChainDefinitionMap.put("/js/**","anon");
       filterChainDefinitionMap.put("/fonts/**","anon");
       filterChainDefinitionMap.put("/img/**","anon");


       filterChainDefinitionMap.put("/logout","logout");
       // 所有请求都要经过 jwt过滤器
       filterChainDefinitionMap.put("/**", "jwt");


       shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

       return shiroFilterFactoryBean;
   }

   @Bean
   public SecurityManager securityManager(){
       //配置SecurityManager,并注入shiroRealm
       DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
       securityManager.setRealm(shiroRealm());
       securityManager.setCacheManager(cacheManager());
       return securityManager;
   }

   @Bean
   public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
       AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
       authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
       return authorizationAttributeSourceAdvisor;
   }

   @Bean
   public ShiroRealm shiroRealm(){
       // 配置Realm,需自己实现
       ShiroRealm shiroRealm = new ShiroRealm();
       return shiroRealm;
   }

   //配置redis缓存
   public RedisManager redisManager() {
       RedisManager redisManager = new RedisManager();
       redisManager.setExpire(60);
       return redisManager;
   }

   public RedisCacheManager cacheManager() {
       RedisCacheManager redisCacheManager = new RedisCacheManager();
       redisCacheManager.setRedisManager(redisManager());
       return redisCacheManager;
   }
}

  1. JWT Token生成
public class JWTUtil {

   private static Logger log = LoggerFactory.getLogger(JWTUtil.class);

   private static final long EXPIRE_TIME = SpringContextUtil.getBean(SystemProperties.class).getJwtTimeOut() * 1000;

   /**
    * 校验 token是否正确
    *
    * @param token  密钥
    * @param secret 用户的密码
    * @return 是否正确
    */
   public static boolean verify(String token, String username, String secret) {
       try {
           Algorithm algorithm = Algorithm.HMAC256(secret);
           JWTVerifier verifier = JWT.require(algorithm)
                   .withClaim("username", username)
                   .build();
           verifier.verify(token);
           log.info("token is valid");
           return true;
       } catch (Exception e) {
           log.info("token is invalid{}", e.getMessage());
           return false;
       }
   }

   /**
    * 从 token中获取用户名
    *
    * @return token中包含的用户名
    */
   public static String getUsername(String token) {
       try {
           DecodedJWT jwt = JWT.decode(token);
           return jwt.getClaim("username").asString();
       } catch (JWTDecodeException e) {
           log.error("error:{}", e.getMessage());
           return null;
       }
   }

   /**
    * 生成 token
    *
    * @param username 用户名
    * @param secret   用户的密码
    * @return token
    */
   public static String sign(String username, String secret) {
       try {
           username = StringUtils.lowerCase(username);
           Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
           Algorithm algorithm = Algorithm.HMAC256(secret);
           return JWT.create()
                   .withClaim("username", username)
                   .withExpiresAt(date)
                   .sign(algorithm);
       } catch (Exception e) {
           log.error("error:{}", e);
           return null;
       }
   }
}

实现单点登录

SSO(Single Sign On):在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。
SSO只有登录模块,没有其他的业务模块,其他业务程序没有登录模块。

现假设存在两个独立的应用程序app1,app2及SSO程序,用户在SSO中登录之后,存在如下问题:

  • Cookie是不能跨域的,我们Cookie的domain属性是sso.a.com,在给app1.a.com和app2.a.com发送请求是带不上的。
  • sso、app1和app2是不同的应用,它们的session存在自己的应用内,是不共享的。

针对第一个问题,sso登录以后,可以将Cookie的域设置为顶域,即.a.com,这样所有子域的系统都可以访问到顶域的Cookie。

这里就要把3个系统的Session共享,如图所示。共享Session的解决方案有很多,例如:Spring-Session。这样第2个问题也解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值