♥♥哈哈哈我来了
今天我们来讲讲使用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 好啦,写了这么久终于写完了,,,,好累哦。。。 有问题欢迎在评论去评论,,,,感谢你的浏览。。。。么么哒