今天研究一下 经常使用的安全框架shirocss
先好好研究一下shiro 的实现原理,方便后面的学习html
shiro (java安全框架)java
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、受权、密码和会话管理。使用Shiro的易于理解的API,您能够
快速、轻松地得到任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
软件名称 Apache Shiro 开发商 Apache 性质 Java安全框架
复制代码
主要功能 三个核心组件:Subject, SecurityManager 和 Realms.git
Subject:即“当前操做用户”。可是,在Shiro中,Subject这一律念并不只仅指人,也能够是第三方进程、后台账户
(Daemon Account)或其余相似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你能够把它认为
是Shiro的“用户”概念。Subject表明了当前用户的安全操做,SecurityManager则管理全部用户的安全操做。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro经过SecurityManager来管理内部组件实例,并经过它来
提供安全管理的各类服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“链接器”。也就是说,当对用户执行认证(登陆)和受权(访问控
制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它
封装了数据源的链接细节,并在须要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(
或)受权。配置多个Realm是能够的,可是至少须要一个。 Shiro内置了能够链接大量安全数据源(又名目录)的Realm,
如LDAP、关系数据库(JDBC)、相似INI的文本配置资源以及属性文件等。若是缺省的Realm不能知足需求,你还能够插入表明
自定义数据源的本身的Realm实现。
复制代码
shiro 原理剖析: shiro的核心是java servlet规范中的filter,经过配置拦截器,使用拦截器链来拦截请求,若是容许访问,则经过。一般状况下,系统的登陆、退出会配置拦截器。登陆的时候,调用subject.login(token),token是用户验证信息,这个时候会在Realm中doGetAuthenticationInfo方法中进行认证。这个时候会把用户提交的验证信息与数据库中存储的认证信息进行比较,一致则容许访问,并在浏览器种下这次回话的cookie,在服务器端存储session信息。退出的时候,调用subject.logout(),会清除回话信息。github
shiro中核心概念介绍: Filter:redis
1.AnonymousFilter:经过此filter修饰的url,任何人均可以进行访问,即便没有进行权限认证spring
2.FormAuthenticationFilter:经过此filter修饰的url,会对请求的url进行验证,若是没有经过,则会重定向返回到loginurlsql
3.BasicHttpAuthenticationFilter:经过此filter修饰的url,要求用户已经经过认证,若是没有经过,则会要求经过Authorization 信息进行认证数据库
4.LogoutFilter:经过此filter修饰的url,一旦收到url请求,则会当即调用subject进行退出,并重定向到redirectUrlapache
5.NoSessionCreationFilter:经过此filter修饰的url,不会建立任何会话
6.PermissionAuthorizationFilter:权限拦截器,验证用户是否具备相关权限
7.PortFilter:端口拦截器,不是经过制定端口访问url,将自动将端口重定向到指定端口
8.HttpMethodPermissionFilter:rest风格拦截器,配置rest的访问方式
9.RolesAuthorizationFilter:角色拦截器,未登录,将跳转到loginurl,未受权,将跳转到unauthorizedUrl
10.SslFilter:HTTPS拦截器,须要以HTTPS的方式进行访问
11.UserFilter:用户拦截器,须要用户已经认证,或已经remember me
拦截配置说明:
anon:例子/admins/**=anon 没有参数,表示能够匿名使用。
authc:例如/admins/user/**=authc表示须要认证(登陆)才能使用,没有参数
roles:例子/admins/user/**=roles[admin],参数能够写多个,多个时必须加上引号,而且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每一个参数经过才算经过,至关于hasAllRoles()方法。
perms:例子/admins/user/**=perms[user:add:*],参数能够写多个,多个时必须加上引号,而且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每一个参数都经过才经过,想当于isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根据请求的方法,至关于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操做时不作检查
注:anon,authcBasic,auchc,user是认证过滤器,
perms,roles,ssl,rest,port是受权过滤器
复制代码
shiro 的权限控制只是作到资源的权限控制,要想实现业务数据的权限控制,确定是须要耦合到咱们具体的业务代码里面的,后面有时间在分享一下如今本身公司的一种解决思路。
上面简单介绍了一下shiro,接下来就进入正题。
加入须要的依赖
<!-- spring-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--模板引擎,访问静态资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- shiro 相关包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro+redis缓存插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
</dependency>
<!--shiro与thymelef的集成插件-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
复制代码
yml文件在加入一点配置,这里要注意的是jpa 和以前的datasource 处于同一级
#经过jpa 生成数据库表
spring:
jpa:
hibernate:
ddl-auto: update
show-sql: true
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
复制代码
数据库设计 通常的权限管理都会设计到这五张表(用户表、角色表、用户角色中间表、权限表、角色权限中间表)
一、用户表:
@Entity //实体类的注解
@Table(name="sys_user")
public class SysUser implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String userName;
private String passWord;
private int userEnable;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public int getUserEnable() {
return userEnable;
}
public void setUserEnable(int userEnable) {
this.userEnable = userEnable;
}
}
复制代码
二、角色表
@Entity //实体类的注解
@Table(name="sys_role")
public class SysRole implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String roleName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
复制代码
三、用户角色中间表
@Entity //实体类的注解
@Table(name="sys_user_role")
public class SysUserRole implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int userId;
private int roleId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getRoleId() {
return roleId;
}
public void setRoleId(int roleId) {
this.roleId = roleId;
}
}
复制代码
四、权限表
@Entity //实体类的注解
@Table(name="sys_permission")
public class SysPermission implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String userName;
private String resUrl;
private String userType;
private String parentId;
private String userSort;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getResUrl() {
return resUrl;
}
public void setResUrl(String resUrl) {
this.resUrl = resUrl;
}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getUserSort() {
return userSort;
}
public void setUserSort(String userSort) {
this.userSort = userSort;
}
}
复制代码
五、角色权限中间表
@Entity //实体类的注解
@Table(name="sys_role_permission")
public class SysRolePermission implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int roleId;
private int permissionId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getRoleId() {
return roleId;
}
public void setRoleId(int roleId) {
this.roleId = roleId;
}
public int getPermissionId() {
return permissionId;
}
public void setPermissionId(int permissionId) {
this.permissionId = permissionId;
}
}
复制代码
基本上五个表的结构就是这样,有问题到时候再改,实体类建好了,先建数据库也行都同样,如今先建了实体类就经过spring-data-jpa 生成一下数据库的表结构 这时候在重启项目就能够帮咱们在数据库建好表了。
shiro 配置类
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 没有登录的用户只能访问登录页面
shiroFilterFactoryBean.setLoginUrl("/auth/login");
// 登陆成功后要跳转的连接
shiroFilterFactoryBean.setSuccessUrl("/auth/index");
// 未受权界面; ----这个配置了没卵用,具体缘由想深刻了解的能够自行百度
shiroFilterFactoryBean.setUnauthorizedUrl("/auth/err");
//自定义拦截器
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
shiroFilterFactoryBean.setFilters(filtersMap);
// 权限控制map.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/auth/login", "anon");
filterChainDefinitionMap.put("/auth/logout", "logout");
filterChainDefinitionMap.put("/auth/kickout", "anon");
//filterChainDefinitionMap.put("/book/**", "authc,perms[book:list],roles[admin]");
//filterChainDefinitionMap.put("/**", "authc,kickout");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 身份认证realm; (这个须要本身写,帐号密码校验;权限等)
*
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost("localhost");
redisManager.setPort(6379);
redisManager.setExpire(1800);// 配置缓存过时时间
redisManager.setTimeout(0);
// redisManager.setPassword(password);
return redisManager;
}
/**
* Session Manager
* 使用的是shiro-redis开源插件
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 经过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/***
* 受权所用配置
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/***
* 使受权注解起做用不如不想配置能够在pom文件中加入
* <dependency>
*<groupId>org.springframework.boot</groupId>
*<artifactId>spring-boot-starter-aop</artifactId>
*</dependency>
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命周期处理器
*
*/
@Bean
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
复制代码
自定义Realm:
public class MyShiroRealm extends AuthorizingRealm {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
//若是项目中用到了事物,@Autowired注解会使事物失效,能够本身用get方法获取值
@Autowired
private SysRoleService roleService;
@Autowired
private UserService userService;
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
logger.info("---------------- 执行 Shiro 凭证认证 ----------------------");
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String name = token.getUsername();
String password = String.valueOf(token.getPassword());
SysUser user = new SysUser();
user.setUserName(name);
user.setPassWord(password);
// 从数据库获取对应用户名密码的用户
SysUser userList = userService.getUser(user);
if (userList != null) {
// 用户为禁用状态
if (userList.getUserEnable() != 1) {
throw new DisabledAccountException();
}
logger.info("---------------- Shiro 凭证认证成功 ----------------------");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userList, //用户
userList.getPassWord(), //密码
getName() //realm name
);
return authenticationInfo;
}
throw new UnknownAccountException();
}
/**
* 受权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("---------------- 执行 Shiro 权限获取 ---------------------");
Object principal = principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
if (principal instanceof SysUser) {
SysUser userLogin = (SysUser) principal;
Set<String> roles = roleService.findRoleNameByUserId(userLogin.getId());
authorizationInfo.addRoles(roles);
Set<String> permissions = userService.findPermissionsByUserId(userLogin.getId());
authorizationInfo.addStringPermissions(permissions);
}
logger.info("---- 获取到如下权限 ----");
logger.info(authorizationInfo.getStringPermissions().toString());
logger.info("---------------- Shiro 权限获取成功 ----------------------");
return authorizationInfo;
}
/**
* 清除全部用户受权信息缓存.
*/
public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}
/**
* 清除全部用户受权信息缓存.
*/
public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}
/**
* @Description: TODO 清楚缓存的受权信息
* @return void 返回类型
*/
public void clearAuthz(){
this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
}
}
复制代码
在数据库里面插入几条数据开始测试。
就拿以前的代码作测试了,先在配置里配置好须要作权限过滤的路径,和权限规则
这样设置应该是必须登陆才能访问,浏览器直接访问一下
在访问一下以前的连接,发现能够正常访问到,查到了以前的测试数据
改个角色的权限试试在,以前咱们已经给用户设置了 ‘admin’ ‘test’两个角色 , 没有设置‘demo’这个角色,请求应该也会被拦截
果真又跳到了登陆页面,把‘demo’ 去掉,发现能够正常请求查到了数据
其实除了在shiro的配置文件配置过滤规则,也能够经过注解的方式在controller上加入权限,效果是同样的
图中框起来的地方能够设置 AND 或者是 OR , 就是设置多个角色的时候, 是所有知足仍是知足一个便可
基于角色的权限设置粒度还比较粗,能够在细一点,针对每一个功能进行设置,这时候就用到了那张权限表
还用刚才的作测试,设置两个权限,咱们在数据库设置的权限是 ‘book:*’ , 测试发现没问题能够请求到
其实除了这种配置方式,还能够经过注解的方式,配置方式相似角色的设置
这样权限配置就差很少了,还有种状况就是要对页面中按钮之类的进行权限控制,作法其实也比较简单
在thymelef 下使用html 进行测试,须要的jar 上面已经导入了,在shiro的config中配置ShiroDialect ,这个上面的配置文件也已经配置好了,剩下的就是在页面头部中引入xmlns
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
复制代码
页面里放两个按钮,配置不一样的权限,数据库中的权限改成"book:list",看一下效果
<tr>
<td colspan="2">
<button shiro:hasPermission="book:list" type="reset">
重置
</button>
<button shiro:hasPermission="book:add" type="button" onclick="submit1()">
提交
</button>
</td>
</tr>
复制代码
发现只有拥有权限的按钮才能显示出来,并且查看页面源码发现没有权限的按钮根本就没有生成在页面中
总结一下,了解了上面的这一系列概念和配置,shiro的基本使用应该是没啥问题的了,接下来在研究一下shiro怎么作单点登陆的。