Shiro来和springboot整合到一起,简单的进行shiro的授权处理操作。这个项目,简单的使用了用户的登陆和登出处理. 需要明白shiro就是一个过滤的插件,主要又Subject ,SecurityManager 以及Realm.其中Subject认证的时候,会调用SecurityManager ,然后SecurityManager (可以管理Reaml的验证策略,多个Reaml验证的策略)调用对应的 Realm做认证和授权的操作。
代码地址
这个代码库是用于演示如何通过shiro和springboot整合 ,
https://gitee.com/yellowcong/springboot-demo/tree/master/springboot-shiro
shiro的cas整合
1. shiro的原理
Shiro在权限认证过程中,操作的是Subject的对象。 登陆的时候,实际上,是操作Realm进行用户的验证和授权操作。
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
2. 代码
2.1 配置 Reaml
这个地方,我定义了一个简单的Reaml,需要继承AuthorizingRealm 这个类。
package com.yellowcong.realm;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import com.yellowcong.model.User;
import com.yellowcong.service.UserService;
/**
* 创建日期:2017年9月23日 <br/>
* 创建用户:yellowcong <br/>
* 功能描述:用于授权操作
*/
public class SampleRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 用户授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection paramPrincipalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 根据用户ID查询角色(role),放入到Authorization里。
Set<String> roles = new HashSet<String>(); // 添加用户角色
roles.add("administrator");
info.setRoles(roles);
// 根据用户ID查询权限(permission),放入到Authorization里。
Set<String> permissions = new HashSet<String>(); // 添加权限
permissions.add("/role/**");
info.setStringPermissions(permissions);
return info;
}
/**
* 认证,用户登录
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken paramAuthenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) paramAuthenticationToken;
User user = userService.login(token.getUsername());
// token返回的是一个数组,将char类型转化为String类型
String pswDate = new String(token.getPassword());
// 当用户名 和密码都满足的情况,返回登陆信息
if(pswDate.equals(user.getPassword())){
return new SimpleAuthenticationInfo(token.getUsername(), token.getPassword(), getName());
} else {
// 当没有用户的时候,抛出异常
throw new AuthenticationException();
}
}
}
2.2 配置ShiroConfig
这个就相当于spring-shiro.xml 这个配置文件了,配置了授权了接口信息。
package com.yellowcong.config;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.filter.DelegatingFilterProxy;
import com.yellowcong.realm.SampleRealm;
@Configuration
@PropertySource("classpath:shiro.properties") // 指定读取的配置文件地址
public class ShiroConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
filterRegistration.addInitParameter("targetFilterLifecycle", "true");
filterRegistration.setEnabled(true);
filterRegistration.addUrlPatterns("/*");
return filterRegistration;
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean
public SampleRealm getSampleRealm(){
return new SampleRealm();
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设定realm 用于权限的认证
securityManager.setRealm(this.getSampleRealm());
//用于securityManager 的缓存
securityManager.setCacheManager(new MemoryConstrainedCacheManager());
return securityManager;
}
/**
* shiro的过滤器
* @param securityManager
* @param casFilter
* @param casServerUrlPrefix
* @param shiroServerUrlPrefix
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager,
@Value("${shiro.loginUrl}") String loginUrl, //登陆地址
@Value("${shiro.successUrl}") String successUrl, //登陆成功地址
@Value("${shiro.unauthorizedUrl}") String unauthorizedUrl) { //未授权的地址
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设定认证管理的Manager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//设定登陆地址
shiroFilterFactoryBean.setLoginUrl(loginUrl);
//设定登陆成功跳转的地址
shiroFilterFactoryBean.setSuccessUrl(successUrl);
//设定未授权的地址
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
Map<String,String> chainMap = new LinkedHashMap<>();
chainMap.put("/user/login", "anon"); //登陆界面任何人可以访问
chainMap.put("/error", "anon"); // erro的信息
chainMap.put("/resources/img/**", "anon"); //图片样式可以直接访问
chainMap.put("/resources/js/**", "anon"); //js可以直接访问
chainMap.put("/user/list", "authc"); //用户列表需要授权
chainMap.put("/**", "authc"); // 余下的页面,全都需要进行授权操作
shiroFilterFactoryBean.setFilterChainDefinitionMap(chainMap);
return shiroFilterFactoryBean;
}
}
2.3 配置文件shiro.properties
这个文件用于配置shiro登陆地址,登入成功后的跳转地址,以及未授权的地址,这些地址,是配置ShiroConfig所需要的,我们之所以单独抽取出来,是为了好配置
#shiro.properties
#没有授权的用户
shiro.cas.default.role=NO_AUTH
#登陆地址
shiro.loginUrl=/user/login
#成功地址
shiro.successUrl=/user/list
#未授权的地址
shiro.unauthorizedUrl=/user/unauthorized
2.4 用户登陆与登出处理
登陆和登出,都需要通过SecurityUtils.getSubject()
获取到Subject这个对象, 然后这个subject对象,可以直接获取到session信息,同时subject.login(token); 这个执行的时候,会调用到SecurityManager 和Reaml进行认证和授权的操作。
package com.yellowcong.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 创建日期:2018年5月8日<br/>
* 代码创建:黄聪<br/>
* 功能描述:<br/>
*/
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 用户列表
* @param map
* @return
*/
@RequestMapping("/list")
public String list(ModelMap map){
//获取subject信息
Subject subject = SecurityUtils.getSubject();
String username = subject.getSession(false).getAttribute("username").toString();
map.put("username", username);
return "/user/list";
}
@ResponseBody
@RequestMapping("/unauthorized")
public String unauthorized(){
return "用户未授权";
}
@ResponseBody
@RequestMapping("/page2")
public String testPage2(){
return "测试需要授权的页面";
}
/**
* 跳转到登录页面
* 创建日期:2017年9月23日<br/>
* 创建用户:yellowcong<br/>
* 功能描述:
* @return
*/
@RequestMapping(value="/login",method=RequestMethod.GET)
public String loginInput(){
return "user/loginInput";
}
/**
* 退出操作
* @return
*/
@RequestMapping(value="/logout")
public String logout(){
//登出系统
SecurityUtils.getSubject().logout();
return "user/loginInput";
}
/**
* 创建日期:2017年9月23日<br/>
* 创建用户:yellowcong<br/>
* 功能描述:用户登录操作
* @param username
* @param password
* @return
*/
@RequestMapping(value="/login",method=RequestMethod.POST)
public String login(String username,String password){
try {
//获取subject信息
Subject subject = SecurityUtils.getSubject();
//判断用户是否授权,解决已经登陆授权的用户,重新登陆授权
if(!subject.isAuthenticated()){
//用户登录
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//保存session
token.setRememberMe(true);
subject.getSession().setAttribute("username", username);
//登录用户
subject.login(token);
}
} catch (Exception e) {
return "/user/error";
}
return "redirect:/user/list";
}
@ResponseBody
@RequestMapping("/error")
public String error(){
return "认证失败";
}
}
2.5 pom.xml配置
pom.xml直接就是引用了shiro的插件。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ted.com</groupId>
<artifactId>springboot-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-shiro-cas</name>
<url>http://maven.apache.org</url>
<!-- 引用父类依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<fastjson.version>1.2.47</fastjson.version>
<log4j.version>1.2.17</log4j.version>
<java.cas.client.version>3.5.0</java.cas.client.version>
<shiro.version>1.2.5</shiro.version>
<ehcache.version>2.4.3</ehcache.version>
<ganymed.version>262</ganymed.version>
<webflow.version>2.4.4.RELEASE</webflow.version>
<druid.version>1.1.0</druid.version>
<mybatis.version>1.3.1</mybatis.version>
</properties>
<dependencies>
<!-- 添加spring-web的依赖,直接就可以使用springmvc了 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 导入thymeleaf模版 的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 导入邮件系统 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- spring.thymeleaf.mode=LEGACYHTML5 -->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
<!-- 添加数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 添加mybatis的依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- 配置日志信息 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-binding</artifactId>
<version>${webflow.version}</version>
</dependency>
<!-- 添加热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</scope>
<!--optional我没弄明白,都说必须为true,但我测试true,false,不加都可以 -->
<optional>true</optional>
</dependency>
<!-- 导入hibernate验证包 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--cas的客户端 -->
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>${java.cas.client.version}</version>
</dependency>
<!-- 服务的ssh客户端 -->
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>${ganymed.version}</version>
</dependency>
<!-- 添加Shiro权限控制 -->
<!-- shiro 配置开始 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- 缓存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>${ehcache.version}</version>
</dependency>
<!-- 调度器,用来查看session是否失效 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- Shiro 配置结束 -->
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<!-- 添加spring的插件, 就可以直接通过 mvn spring-boot:run 运行了 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--在这里添加 springloader plugin,热部署 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>