服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|JavaScript|易语言|

服务器之家 - 编程语言 - JAVA教程 - spring boot整合Shiro实现单点登录的示例代码

spring boot整合Shiro实现单点登录的示例代码

2021-03-27 13:43牛奋lch JAVA教程

本篇文章主要介绍了spring boot整合Shiro实现单点登录的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

Shiro是什么

Shiro是一个Java平台的开源权限框架,用于认证和访问授权。具体来说,满足对如下元素的支持:

  1. 用户,角色,权限(仅仅是操作权限,数据权限必须与业务需求紧密结合),资源(url)。
  2. 用户分配角色,角色定义权限。
  3. 访问授权时支持角色或者权限,并且支持多级的权限定义。

Q:对组的支持?
A:shiro默认不支持对组设置权限。

Q:是否可以满足对组进行角色分配的需求?
A:扩展Realm,可以支持对组进行分配角色,其实就是给该组下的所有用户分配权限。

Q:对数据权限的支持? 在业务系统中定义?
A:shiro仅仅实现对操作权限的控制,用于在前端控制元素隐藏或者显示,以及对资源访问权限进行检查。数据权限与具体的业务需求紧密关联,shiro本身无法实现对数据权限的控制。

Q:动态权限分配?
A:扩展org.apache.shiro.realm.Realm,支持动态权限分配。

Q:与Spring集成?
A:可以支持与Spring集成,shiro还支持jsp标签。

前面的博客中,我们说道了Shiro的两个最大的特点,认证和授权,而单点登录也是属于认证的一部分,默认情况下,Shiro已经为我们实现了和Cas的集成,我们加入集成的一些配置就ok了。

1、加入shiro-cas包

?
1
2
3
4
5
6
<!-- shiro整合cas单点 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-cas</artifactId>
      <version>1.2.4</version>
    </dependency>

2、加入单点登录的配置

这里,我将所有的配置都贴出来,方便参考,配置里面已经加了详尽的说明。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
package com.chhliu.springboot.shiro.config; 
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasSubjectFactory;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.DelegatingFilterProxy;
 
/**
 * Shiro 配置
 *
 * Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 既然是使用
 * Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
 *
 * @author chhliu
 */
@Configuration
public class ShiroConfiguration {
   
  // cas server地址
  public static final String casServerUrlPrefix = "http://127.0.0.1";
  // Cas登录页面地址
  public static final String casLoginUrl = casServerUrlPrefix + "/login";
  // Cas登出页面地址
  public static final String casLogoutUrl = casServerUrlPrefix + "/logout";
  // 当前工程对外提供的服务地址
  public static final String shiroServerUrlPrefix = "http://127.0.1.28:8080";
  // casFilter UrlPattern
  public static final String casFilterUrlPattern = "/index";
  // 登录地址
  public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern;
  // 登出地址(casserver启用service跳转功能,需在webapps\cas\WEB-INF\cas.properties文件中启用cas.logout.followServiceRedirects=true)
  public static final String logoutUrl = casLogoutUrl+"?service="+loginUrl;
  // 登录成功地址
//  public static final String loginSuccessUrl = "/index";
  // 权限认证失败跳转地址
  public static final String unauthorizedUrl = "/error/403.html";
   
  /**
   * 实例化SecurityManager,该类是shiro的核心类
   * @return
   */
  @Bean
  public DefaultWebSecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(myShiroCasRealm());
//   <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
    securityManager.setCacheManager(getEhCacheManager());
    // 指定 SubjectFactory,如果要实现cas的remember me的功能,需要用到下面这个CasSubjectFactory,并设置到securityManager的subjectFactory中
    securityManager.setSubjectFactory(new CasSubjectFactory());
    return securityManager;
  }
 
  /**
   * 配置缓存
   * @return
   */
  @Bean
  public EhCacheManager getEhCacheManager() {
    EhCacheManager em = new EhCacheManager();
    em.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
    return em;
  }
 
  /**
   * 配置Realm,由于我们使用的是CasRealm,所以已经集成了单点登录的功能
   * @param cacheManager
   * @return
   */
  @Bean
  public MyShiroRealm myShiroCasRealm() {
    MyShiroRealm realm = new MyShiroRealm();
    // cas登录服务器地址前缀
    realm.setCasServerUrlPrefix(ShiroConfiguration.casServerUrlPrefix);
    // 客户端回调地址,登录成功后的跳转地址(自己的服务地址)
    realm.setCasService(ShiroConfiguration.shiroServerUrlPrefix + ShiroConfiguration.casFilterUrlPattern);
    // 登录成功后的默认角色,此处默认为user角色
    realm.setDefaultRoles("user");
    return realm;
  }
 
  /**
   * 注册单点登出的listener
   * @return
   */
  @SuppressWarnings({ "rawtypes", "unchecked" })
  @Bean
  @Order(Ordered.HIGHEST_PRECEDENCE)// 优先级需要高于Cas的Filter
  public ServletListenerRegistrationBean<?> singleSignOutHttpSessionListener(){
    ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
    bean.setListener(new SingleSignOutHttpSessionListener());
    bean.setEnabled(true);
    return bean;
  }
 
  /**
   * 注册单点登出filter
   * @return
   */
  @Bean
  public FilterRegistrationBean singleSignOutFilter(){
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setName("singleSignOutFilter");
    bean.setFilter(new SingleSignOutFilter());
    bean.addUrlPatterns("/*");
    bean.setEnabled(true);
    return bean;
  }
 
  /**
   * 注册DelegatingFilterProxy(Shiro)
   */
  @Bean
  public FilterRegistrationBean delegatingFilterProxy() {
    FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
    filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
    // 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
    filterRegistration.addInitParameter("targetFilterLifecycle", "true");
    filterRegistration.setEnabled(true);
    filterRegistration.addUrlPatterns("/*");
    return filterRegistration;
  }
 
  /**
   * 该类可以保证实现了org.apache.shiro.util.Initializable接口的shiro对象的init或者是destory方法被自动调用,
   * 而不用手动指定init-method或者是destory-method方法
   * 注意:如果使用了该类,则不需要手动指定初始化方法和销毁方法,否则会出错
   * @return
   */
  @Bean(name = "lifecycleBeanPostProcessor")
  public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
  }
 
  /**
   * 下面两个配置主要用来开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
   * @return
   */
  @Bean
  @DependsOn("lifecycleBeanPostProcessor")
  public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
    daap.setProxyTargetClass(true);
    return daap;
  }
   
  /**
   * @param securityManager
   * @return
   */
  @Bean
  public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
  }
 
  /**
   * CAS过滤器
   * @return
   */
  @Bean(name = "casFilter")
  public CasFilter getCasFilter() {
    CasFilter casFilter = new CasFilter();
    casFilter.setName("casFilter");
    casFilter.setEnabled(true);
    // 登录失败后跳转的URL,也就是 Shiro 执行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer验证tiket
    casFilter.setFailureUrl(loginUrl);// 我们选择认证失败后再打开登录页面
    casFilter.setLoginUrl(loginUrl);
    return casFilter;
  }
 
  /**
   * 使用工厂模式,创建并初始化ShiroFilter
   * @param securityManager
   * @param casFilter
   * @return
   */
  @Bean(name = "shiroFilter")
  public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasFilter casFilter) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    // 必须设置 SecurityManager
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
    shiroFilterFactoryBean.setLoginUrl(loginUrl);
    /*
     * 登录成功后要跳转的连接,不设置的时候,会默认跳转到前一步的url
     * 比如先在浏览器中输入了http://localhost:8080/userlist,但是现在用户却没有登录,于是会跳转到登录页面,等登录认证通过后,
     * 页面会再次自动跳转到http://localhost:8080/userlist页面而不是登录成功后的index页面
     * 建议不要设置这个字段
     */
//    shiroFilterFactoryBean.setSuccessUrl(loginSuccessUrl);
     
    // 设置无权限访问页面
    shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
    /*
     * 添加casFilter到shiroFilter中,注意,casFilter需要放到shiroFilter的前面,
     * 从而保证程序在进入shiro的login登录之前就会进入单点认证
     */
    Map<String, Filter> filters = new LinkedHashMap<>();
    filters.put("casFilter", casFilter);
     
    // logout已经被单点登录的logout取代
    // filters.put("logout",logoutFilter());
    shiroFilterFactoryBean.setFilters(filters);
 
    loadShiroFilterChain(shiroFilterFactoryBean);
    return shiroFilterFactoryBean;
  }
 
  /**
   * 加载shiroFilter权限控制规则(从数据库读取然后配置),角色/权限信息由MyShiroCasRealm对象提供doGetAuthorizationInfo实现获取来的
   * 生产中会将这部分规则放到数据库中
   * @param shiroFilterFactoryBean
   */
  private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
    /////////////////////// 下面这些规则配置最好配置到配置文件中,注意,此处加入的filter需要保证有序,所以用的LinkedHashMap ///////////////////////
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); 
    filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter"); 
    //2.不拦截的请求
    filterChainDefinitionMap.put("/css/**","anon");
    filterChainDefinitionMap.put("/js/**","anon");
    filterChainDefinitionMap.put("/login", "anon");
    // 此处将logout页面设置为anon,而不是logout,因为logout被单点处理,而不需要再被shiro的logoutFilter进行拦截
    filterChainDefinitionMap.put("/logout","anon");
    filterChainDefinitionMap.put("/error","anon");
    //3.拦截的请求(从本地数据库获取或者从casserver获取(webservice,http等远程方式),看你的角色权限配置在哪里)
    filterChainDefinitionMap.put("/user", "authc"); //需要登录
    //4.登录过的不拦截
    filterChainDefinitionMap.put("/**", "authc");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
  }
}

部分配置参考:http://shiro.apache.org/spring.html

3、编写Realm

由于需要集成单点登录的功能,所以需要集成CasRealm类,该类已经为我们实现了单点认证的功能,我们要做的就是实现授权部分的功能,示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.chhliu.springboot.shiro.config; 
import javax.annotation.Resource; 
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.subject.PrincipalCollection;
 
import com.chhliu.springboot.shiro.mode.SysPermission;
import com.chhliu.springboot.shiro.mode.SysRole;
import com.chhliu.springboot.shiro.mode.UserInfo;
import com.chhliu.springboot.shiro.service.UserInfoService;
 
/**
 * 权限校验核心类; 由于使用了单点登录,所以无需再进行身份认证 只需要授权即可
 *
 * @author chhliu
 */
public class MyShiroRealm extends CasRealm {
 
  @Resource
  private UserInfoService userInfoService;
 
  /**
   * 1、CAS认证 ,验证用户身份
   * 2、将用户基本信息设置到会话中,方便获取
   * 3、该方法可以直接使用CasRealm中的认证方法,此处仅用作测试
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
 
    // 调用父类中的认证方法,CasRealm已经为我们实现了单点认证。
    AuthenticationInfo authc = super.doGetAuthenticationInfo(token);
 
    // 获取登录的账号,cas认证成功后,会将账号存起来
    String account = (String) authc.getPrincipals().getPrimaryPrincipal();
 
    // 将用户信息存入session中,方便程序获取,此处可以将根据登录账号查询出的用户信息放到session中
    SecurityUtils.getSubject().getSession().setAttribute("no", account);
 
    return authc;
  }
 
  /**
   * 此方法调用 hasRole,hasPermission的时候才会进行回调.
   *
   * 权限信息.(授权): 1、如果用户正常退出,缓存自动清空; 2、如果用户非正常退出,缓存自动清空;
   * 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。 (需要手动编程进行实现;放在service进行调用)
   * 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例, 调用clearCached方法;
   * :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
   *
   * @param principals
   * @return
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
 
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    // 获取单点登陆后的用户名,也可以从session中获取,因为在认证成功后,已经将用户名放到session中去了
    String userName = (String) super.getAvailablePrincipal(principals);
//       principals.getPrimaryPrincipal(); 这种方式也可以获取用户名
 
    // 根据用户名获取该用户的角色和权限信息
    UserInfo userInfo = userInfoService.findByUsername(userName);
 
    // 将用户对应的角色和权限信息打包放到AuthorizationInfo中
    for (SysRole role : userInfo.getRoleList()) {
      authorizationInfo.addRole(role.getRole());
      for (SysPermission p : role.getPermissions()) {
        authorizationInfo.addStringPermission(p.getPermission());
      }
    }
 
    return authorizationInfo;
  }
}

下面,我们就可以进行验证测试了!

在浏览器输入http:127.0.1.28:8080/userInfo/userList 我们会发现,会自动跳转到单点的登录页面

spring boot整合Shiro实现单点登录的示例代码

然后我们输入用户名和密码,就会自动跳转到http:127.0.1.28:8080/userInfo/userList页面了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://blog.csdn.net/liuchuanhong1/article/details/76850181

延伸 · 阅读

精彩推荐
  • JAVA教程java webApp异步上传图片实现代码

    java webApp异步上传图片实现代码

    这篇文章主要为大家详细介绍了java webApp异步上传图片实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    ChaudementWu1482020-06-30
  • JAVA教程Mybatis实现增删改查及分页查询的方法

    Mybatis实现增删改查及分页查询的方法

    MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持 久层框架,通过本文给大家介绍Mybatis实现增删改查及分页查询的方法,感兴趣的朋友一起学习吧 ...

    MRR2472020-03-23
  • JAVA教程C/C++和Java的交互详解

    C/C++和Java的交互详解

    本文旨在简单的介绍一下如何让java层和C/C++层相互调用,这里主要是使用了JNI技术,并没有深究其原理,只是做了个实现,其目的是为后面的学习打开一扇...

    hebedich2652020-03-25
  • JAVA教程Java实现调用MySQL存储过程详解

    Java实现调用MySQL存储过程详解

    相信大家都知道存储过程是在大型数据库系统中,一组为了完成特定功能的SQL语句集。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应...

    shootercheng2992020-07-05
  • JAVA教程Java使用递归解决算法问题的实例讲解

    Java使用递归解决算法问题的实例讲解

    递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解,这里我们就来看几个Java使用递归解决算法问题的实例讲解...

    it_wangxiangpan1532020-05-14
  • JAVA教程解决调试JDK源码时,不能查看变量的值问题

    解决调试JDK源码时,不能查看变量的值问题

    下面小编就为大家带来一篇解决调试JDK源码时,不能查看变量的值问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看...

    Java教程网4262020-11-13
  • JAVA教程Java线程安全解决方案(synchronized,ReentrantLock,Atomic)

    Java线程安全解决方案(synchronized,ReentrantLock,Atomic)

    这篇文章主要介绍了Java线程安全解决方案(synchronized,ReentrantLock,Atomic),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值...

    雨developer3292020-09-28
  • JAVA教程Java实现排球比赛计分系统

    Java实现排球比赛计分系统

    这篇文章主要为大家详细介绍了Java实现排球比赛计分系统,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    懒懒的4972020-11-20