Apache Shiro 是一个功能强大且灵活的开放源代码安全框架,能够细粒度地处理认证 (Authentication),受权 (Authorization),会话 (Session) 管理和加密 (cryptography) 等企业级应用中常见的安全控制流程。 Apache Shiro 的首要目标是易于使用和理解。 有时候安全性的流程控制会很是复杂,对开发人员来讲是件很头疼的事情,但并不必定如此。 框架就应该尽量地掩盖复杂性,并公开一个简洁而直观的 API,从而简化开发人员的工做,确保其应用程序安全性。此次咱们聊一聊如何在 Spring Web 应用中使用 Shiro 实现权限控制。java
Apache Shiro 是一个具备许多功能的综合型应用程序安全框架。 下图为 Shiro 中的最主要的几个功能: web
从总体概念上理解,Shiro 的体系架构有三个主要的概念:Subject (主体,也就是用户),Security Manager (安全管理器)和 Realms (领域)。 下图描述了这些组件之间的关系: 算法
在 Web 应用中,对安全的控制主要有角色、资源、权限(什么角色能访问什么资源)几个概念,一个用户能够有多个角色,一个角色也能够访问多个资源,也就是角色能够对应多个权限。落实到数据库设计上,咱们至少须要建 5 张表:用户表、角色表、资源表、角色-资源表、用户-角色表,这 5 张表的结构以下: 用户表:spring
id | username | password |
---|---|---|
1 | 张三 | 123456 |
2 | 李四 | 666666 |
3 | 王五 | 000000 |
角色表:数据库
id | rolename |
---|---|
1 | 管理员 |
2 | 经理 |
3 | 员工 |
资源表:apache
id | resname |
---|---|
1 | /user/add |
2 | /user/delete |
3 | /compony/info |
角色-资源表:缓存
id | roleid | resid |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 2 | 3 |
用户-角色表:安全
id | userid | roleid |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 1 | 3 |
对应的 POJO 类以下:session
/** * 用户 */
public class User {
private Integer id;
private String username;
private String password;
//getter & setter...
}
复制代码
/** * 角色 */
public class Role {
private String id;
private String rolename;
}
复制代码
/** * 资源 */
public class Resource {
private String id;
private String resname;
}
复制代码
/** * 角色-资源 */
public class RoleRes {
private String id;
private String roleid;
private String resid;
}
复制代码
/** * 用户-角色 */
public class UserRole {
private String id;
private String userid;
private String roleid;
}
复制代码
Spring 与 Shiro 整合的详细步骤,请参阅个人博客 《 Spring 应用中整合 Apache Shiro 》。 这里补充一下:须要提早引入 Shiro 的依赖,打开 http://www.javashuo.com/tag/mvnrepository.com,搜索 Shiro,咱们须要前三个依赖,也就是 Shiro-Core、Shiro-Web 以及 Shiro-Spring,以 Maven 项目为例,在 pom.xml
中的 <dependencies>
节点下添加以下依赖:架构
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
复制代码
在 application-context.xml
中须要这样配置 shiroFilter
bean:
<!-- 配置shiro的过滤器工厂类,id- shiroFilter要和咱们在web.xml中配置的过滤器一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- 登陆页面 -->
<property name="loginUrl" value="/login"/>
<!-- 登陆成功后的页面 -->
<property name="successUrl" value="/index"/>
<!-- 非法访问跳转的页面 -->
<property name="unauthorizedUrl" value="/403"/>
<!-- 权限配置 -->
<property name="filterChainDefinitions">
<value>
<!-- 无需认证便可访问的静态资源,还能够添加其余 url -->
/static/** = anon
<!-- 除了上述忽略的资源,其余全部资源都须要认证后才能访问 -->
/** = authc
</value>
</property>
</bean>
复制代码
接下来就须要定义 Realm 了,自定义的 Realm 集成自 AuthorizingRealm
类:
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/** * 验证权限 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String loginName = SecurityUtils.getSubject().getPrincipal().toString();
if (loginName != null) {
String userId = SecurityUtils.getSubject().getSession().getAttribute("userSessionId").toString();
// 权限信息对象,用来存放查出的用户的全部的角色及权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 用户的角色集合
ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();
info.setRoles(shiroUser.getRoles());
info.addStringPermissions(shiroUser.getUrlSet());
return info;
}
return null;
}
/** * 认证回调函数,登陆时调用 */
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String username = (String) token.getPrincipal();
User user = new User();
sysuser.setUsername(username);
try {
List<SysUser> users = userService.findByNames(user);
List<String> roleList= userService.selectRoleNameListByUserId(users.get(0).getId());
if (users.size() != 0) {
String pwd = users.get(0).getPassword();
// 当验证都经过后,把用户信息放在 session 里
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userSession", users.get(0));
session.setAttribute("userSessionId", users.get(0).getId());
session.setAttribute("userRoles", org.apache.commons.lang.StringUtils.join(roleList,","));
return new SimpleAuthenticationInfo(username,users.get(0).getPassword());
} else {
// 没找到该用户
throw new UnknownAccountException();
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
/** * 更新用户受权信息缓存. */
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
/** * 更新用户信息缓存. */
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
/** * 清除用户受权信息缓存. */
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
/** * 清除用户信息缓存. */
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
/** * 清空全部缓存 */
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
/** * 清空全部认证缓存 */
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}
复制代码
最后定义一个用户登陆的控制器,接受用户的登陆请求:
@Controller
public class UserController {
/** * 用户登陆 */
@PostMapping("/login")
public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){
try {
if(bindingResult.hasErrors()){
return "login";
}
//使用权限工具进行认证,登陆成功后跳到 shiroFilter bean 中定义的 successUrl
SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
return "redirect:index";
} catch (AuthenticationException e) {
redirectAttributes.addFlashAttribute("message","用户名或密码错误");
return "redirect:login";
}
}
/** * 注销登陆 */
@GetMapping("/logout")
public String logout(RedirectAttributes redirectAttributes ){
SecurityUtils.getSubject().logout();
return "redirect:login";
}
}
复制代码