Shiro实现权限认证

♥♥哈哈哈我来了

今天我们来讲讲使用Shiro来实现用户登陆权限

简介:Shiro

介绍:
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

主要功能
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
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实现。

直接走上正题:

第一步:依旧是引入我们的Pom文件

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.2</version>
<exclusions>
<exclusion>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>

是不是感觉很熟悉啊,这些都是我们常用的Pom文件哦。。

第二步:application.yml文件就不用多少来吧。。还是上代码吧:

​ 启动端口

server:
port: 8888
spring:
application:
name: demo-shiro

这也太简单了吧,使用SpringBoot开发就是这么好哦。。

第四步:注意了,开胃菜来了。。。。

创建User对象:(User)

package com.demo.shiro.entity;

import lombok.Data;

import java.util.Set;

@Data
public class User {

/**
* 用户Id
*/
private Long userId;

/**
* 用户名称
*/
private String userName;

/**
* 用户密码
*/
private String userPassword;

/**
* 用户角色
*/
private Set<String> role;

/**
* 用户权限
*/
private Set<String> permission;

/**
* 构造器
* @param userId
* @param userName
* @param userPassword
* @param role
* @param permission
*/
public User(Long userId, String userName, String userPassword, Set<String> role, Set<String> permission) {
this.userId = userId;
this.userName = userName;
this.userPassword = userPassword;
this.role = role;
this.permission = permission;
}
}

创建数据响应:(Response)

package com.demo.shiro.entity;

import java.util.HashMap;
import java.util.Map;

/**
* 响应数据
*/
public class Response extends HashMap<String, Object> {
/**
* 信息
* @param message
* @return
*/
public Response message(String message){

this.put("message", message);

return this;
}

/**
* 数据
* @param data
* @return
*/
public Response data(Object data){

this.put("data", data);

return this;
}

/**
* 重新方法
* @param key
* @param value
* @return
*/
@Override
public Object put(String key, Object value) {

super.put(key, value);

return this;
}
}

创建我们自定义的异常(SystemException)

package com.demo.shiro.exception;

/**
* 异常抛出
*/
public class SystemException extends Exception{

public SystemException(String messgae){

super(messgae);
}
}

创建一个系统工具类类模拟数据信息:(SystemUtil):

package com.demo.shiro;

import com.demo.shiro.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import javax.lang.model.type.ArrayType;
import java.util.*;

/**
* 模拟数据库数据
*/
public class SystemUtils {
/**
* 模拟数据库中两条数据
*
* @return
*/
public static List<User> users() {

List<User> users = new ArrayList<>();

// 添加数据
users.add(new User(
1L,
"admin",
"123456",
new HashSet<>(Collections.singleton("admin")),
new HashSet<>(Arrays.asList("user:add", "user:delete"))));

users.add(new User(
2L,
"register",
"123456",
new HashSet<>(Collections.singleton("register")),
new HashSet<>(Arrays.asList("user:view"))));

return users;
}

/**
* 获取用户
* @param username
* @return
*/
public static User getUser(String username){

List<User> users = SystemUtils.users();

return users.stream().filter(user ->

StringUtils.equalsAnyIgnoreCase(username,user.getUserName())).findFirst().orElse(null);
}
}

OK,到这里我们基本类就全部创建完成了,下面开始主角来了。。。

第五步:创建我们的Shiro相关配置信息

Shiro配置类:(ShiroConfig)

package com.demo.shiro.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
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.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.*;

@Configuration
public class ShiroConfig {

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

// 设置 securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);

return shiroFilterFactoryBean;
}

@Bean
public SecurityManager securityManager(ShiroRealm shiroRealm) {

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

// 配置 SecurityManager,并注入 shiroRealm
securityManager.setRealm(shiroRealm);

return securityManager;
}

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

@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

/**
* 解决注解不生效的问题
* @return
*/
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
return new DefaultAdvisorAutoProxyCreator();
}
}

Shiro认证和授权类(ShiroRelam):

package com.demo.shiro.config;

import com.demo.shiro.SystemUtils;
import com.demo.shiro.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.stereotype.Component;

@Component
public class ShiroRealm extends AuthorizingRealm {

/**
* 登陆模块
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

// 获取用户名
String username = (String) authenticationToken.getPrincipal();

// 获取密码
String password = new String((char[]) authenticationToken.getCredentials());

// 根据用户名获取用户
User user = SystemUtils.getUser(username);

if (user == null || !StringUtils.equals(password, user.getUserPassword())){

throw new IncorrectCredentialsException("用户名或密码错误");
}

// 登陆成功
return new SimpleAuthenticationInfo(user, user.getUserPassword(), getName());
}

/**
* 授权模块
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

User user = (User) SecurityUtils.getSubject().getPrincipal();

SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

// 设置角色,模拟数据库
simpleAuthorizationInfo.setRoles(user.getRole());

// 设置权限
simpleAuthorizationInfo.setStringPermissions(user.getPermission());

return simpleAuthorizationInfo;
}
}

是不是感觉很简单呢,是的,只需要配置这两个类就可以完Shiro了。。。。

####第六步:现在可以开始我们的测试了。。。你准备好了吗?

创建一个登陆页面:(login.html)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆测试</title>
</head>
<body>

<form action="/login" method="post">

<input type="text" name="username">
<br>

<input type="password" name="password">

<br>

<input type="submit" value="登陆">

</form>
</body>
</html>

创建ViewController来访问我们的页面:

package com.demo.shiro.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* 页面控制
**/

@Controller
public class ViewController {

@RequestMapping("login.html")
public String showLogin(){

return "login";
}
}

这里页面不用展示了,这也太简单了吧。。。。

创建我们的登陆Controller(LoginController)

package com.demo.shiro.controller;

import com.demo.shiro.entity.Response;
import com.demo.shiro.exception.SystemException;
import com.demo.shiro.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
* 登陆Controller
*/
@Controller
public class LoginController {

@Autowired(required = false)
private LoginService loginService;

/**
* 登陆
* @param username
* @param password
* @return
*/
@PostMapping("login")
public ResponseEntity<Response> login(@RequestParam("username") String username,
@RequestParam("password") String password) throws SystemException {

Response response = this.loginService.login(username, password);

return ResponseEntity.ok(response);
}
}

可能会有报错,不用慌张,是因为我们的LoginService没有创建。。。

创建我们的LoginService(LoginService):

package com.demo.shiro.service;

import com.demo.shiro.entity.Response;
import com.demo.shiro.exception.SystemException;

public interface LoginService {

// 登陆
Response login(String username, String password) throws SystemException;
}

创建它的实现类(LoginServiceImpl)

package com.demo.shiro.service.impl;

import com.demo.shiro.SystemUtils;
import com.demo.shiro.entity.Response;
import com.demo.shiro.entity.User;
import com.demo.shiro.exception.SystemException;
import com.demo.shiro.service.LoginService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;

/**
* 登陆Service
*/
@Service
public class LoginServiceImpl implements LoginService {

/**
* 登陆操作
* @param username
* @param password
* @return
*/
@Override
public Response login(String username, String password) throws SystemException {

Subject subject = SecurityUtils.getSubject();

// 判断是否存在
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)){

throw new SystemException("请输入信息");
}

try {
// 执行登陆
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);

subject.login(usernamePasswordToken);

User user = (User) subject.getPrincipal();

return new Response().message("认证成功").data(user);

} catch (AuthenticationException e) {

e.printStackTrace();

throw new SystemException("认证失败");
}

}
}

这里面的代码也很容易看懂吧,这代码质量太差了吧。。。。
哈哈哈,需要自己优化哦。。。

到这里,我们的登陆逻辑就完了。。。是不是感觉很容易呢。。。

接下来是我们的测试时间了,,Are you ready?

第七步:测试环境:

创建测试Controller(TestController)

package com.demo.shiro.controller;

import com.demo.shiro.entity.Response;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.security.RolesAllowed;

/**
* 测试Controller
*/
@Controller
@RequestMapping("test")
public class TestController {

/**
* 需要admin角色才能访问
* @return
*/
@GetMapping("admin")
@RequiresRoles("admin")
public ResponseEntity<Response> test1(){

return ResponseEntity.ok(new Response().message("你是admin"));
}

/**
* 拥有查看权限
* @return
*/
@GetMapping("view")
@RequiresPermissions("admin:view")
public ResponseEntity<Response> test2(){

return ResponseEntity.ok(new Response().message("你有查看权限"));
}
}

这里面有两个没有遇见过的注解吧,那么学习了这篇文章你就会了哦。。。。

这里只是实现了登陆,没有设置过滤器、拦截器这些。。。
我们需要登陆过后才能访问页面。。。有没有想过呢。。。
哈哈哈

目录截图:

测试截图:

登陆界面:

使用admin做测试登陆:
登陆成功后我们会看到以下信息

我相信看得懂吧,没错就是我们自己设置的模拟数据哦。。
接下来我们访问其他功能。。。
访问test/admin这个接口:

我们再访问test/view这个接口:

你是否看到这个页面很慌呢,不要慌,我们要稳住,仔细一看,原来我们没有这个权限,所以。。。。懂吧

OK,这里我们演示完了,还有个注册用户,我们可以自己测试。。。
GitHub地址:Shiro-demo
好啦,写了这么久终于写完了,,,,好累哦。。。
有问题欢迎在评论去评论,,,,感谢你的浏览。。。。么么哒