前言
前几天项目中要接入shiro,查看了它的官方教程以及示例代码,可以说已经是非常详细了。但是对于我这种之前没有接触过,马上要上手的情况来说,欠缺了如何在springboot项目中如何接入的教程,网上也有很多很好的博客写了springboot如何接入shiro,毕竟shiro并不是什么新东西,但是我看了许多博客都是配置的比较全的功能的,shiro是一个轻量级的框架,可以自定义添加需要哪些功能。一下子上来太多配置让我有点懵,所以我踩了一下坑之后,记录下了一个比较简单、轻量的接入文章,该项目只是简单的实现了认证功能,鉴权功能并没有深入。
依赖
shiro在maven库中的包有许多个,比如shiro-core、shiro-web、shiro-spring等,因为我是基于springboot来接入shiro,所以我这里添加的是shiro-spring-boot-web-starter 依赖。
<!-- springboot starter 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- shiro 依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.5.3</version>
</dependency>
基础
shiro需要接入用户信息,所以这里写了个简单的service来实现用户信息的查询。
model
package com.martain.shirodemo.model;
public class User {
private String userName;
private String password;
public User(String userName, String password) {
this.userName = userName;
this.password = password;
}
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;
}
}
Service
package com.martain.shirodemo.service;
import com.martain.shirodemo.model.User;
/**
* 用户服务 接口
* @author martain
*/
public interface IUserService {
/**
* 通过用户名来
* @param userName
* @return
*/
User findByUserName(String userName);
}
package com.martain.shirodemo.service.impl;
import com.martain.shirodemo.model.User;
import com.martain.shirodemo.service.IUserService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 用户服务实现
* @author martain
*/
@Service
public class UserServiceImpl implements IUserService {
/**
* 模拟静态数据库
*/
private final static Map<String,User> users = new HashMap<>();
static {
users.put("haitao",new User("haitao","123456"));
users.put("martain",new User("martain","1024"));
}
/**
* 通过用户名查询用户
* @param userName 用户名
* @return 用户记录
*/
@Override
public User findByUserName(String userName) {
User user = users.get(userName);
return user;
}
}
接入shiro
shiro 是非常好用的一个框架,使用他只需要自己添加一些配置即可,如果需要许多的自定义功能只需要简单的重写一些方法或类即可。为了更快的接入,我这里使用了尽可能的少的配置来启动shiro。简单的应用中,shiro的认证原理有如下几个步骤:
- 应用代码通过SecurityUtils.getSubject()来获取到subject
- subject调用一些方法(比如login等)来委托SecurityManager来进行认证和授权。
- SecurityManager通过注入的Realm来获取用户信息来实现认证和授权
自定义Realm
Shiro 认证相关的功能都有SecurityManager 委托给了Realm来实现认证的功能。也可以称他为域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
package com.martain.shirodemo.config;
import com.martain.shirodemo.model.User;
import com.martain.shirodemo.service.IUserService;
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.beans.factory.annotation.Autowired;
/**
* 自定义Realm
* @author martain
*/
public class CustomUserRealm extends AuthorizingRealm {
@Autowired
IUserService userService;
/**
* 鉴权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("doGetAuthorizationInfo...");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
return simpleAuthorizationInfo;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("doGetAuthenticationInfo...");
// 因为我们在后面调用Subject.login时传入的是UsernamePasswordToken类型 所以 AuthenticationToken 是 UsernamePasswordToken 类型
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
String password = String.valueOf(usernamePasswordToken.getPassword());
User user = userService.findByUserName(username);
if (user == null){
throw new AuthenticationException("用户不存在");
}
// 这里只是做了简单的比对
if (!user.getPassword().equalsIgnoreCase(password))
{
throw new AuthenticationException("用户密码错误");
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
return simpleAuthenticationInfo;
}
}
编写ShiroConfig
Shiro配置文件中可以配置许多的东西,但不是都是必须的,所以我这里只是添加了必要的配置:注入Realm、注入SecurityManager以及shiroFilterFactoryBean,当然,如果这些没有配置的话程序也是无法启动的。
package com.martain.shirodemo.config;
import com.sun.org.apache.xerces.internal.util.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* 注入Realm
* @return
*/
@Bean
public CustomUserRealm myUserRealm() {
CustomUserRealm customUserRealm = new CustomUserRealm();
return customUserRealm;
}
/**
* 注入SecurityManager
*
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myUserRealm());
return manager;
}
/**
* Filter 工厂
* -设置过滤条件以及跳转条件
*
* @param securityManager
* @return
*/
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
/**
* anon:匿名用户可访问
* authc:认证用户可访问ShiroFilterFactoryBean
* user:使用rememberMe可访问
* perms:对应权限可访问
* role:对应角色权限可访问
**/
Map<String, String> filterChainMap = new LinkedHashMap<>();
// 登录接口开放
filterChainMap.put("/login", "anon");
// 获取用户信息需要认证用户
filterChainMap.put("/user/**", "authc");
// 未授权时的跳转url,比如跳转到登录页面
bean.setLoginUrl("/login");
// 首页
bean.setSuccessUrl("/index");
// 错误页面
bean.setUnauthorizedUrl("/error");
bean.setFilterChainDefinitionMap(filterChainMap);
return bean;
}
}
测试接口
package com.martain.shirodemo.controller;
import com.martain.shirodemo.service.IUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author martain
*/
@RestController
public class AuthController {
@GetMapping("/login")
public String login(String userName,String password){
/**
* 这里的注入的Token类型 就是 出入Realm中doGetAuthenticationInfo的参数类型
*/
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, password);
Subject subject = SecurityUtils.getSubject();
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
// 检测角色
// subject.checkRole("admin");
// 检测权限
// subject.checkPermissions("query", "add");
} catch (UnknownAccountException e) {
return "用户名不存在!";
} catch (AuthenticationException e) {
System.out.println(e.getLocalizedMessage());
return "账号或密码错误!";
} catch (AuthorizationException e) {
return "没有权限";
}
return "Hello,"+userName;
}
@GetMapping("/user/{token}")
public String getUserInfo(@PathVariable String token){
return "Hello,your token is "+token;
}
}
这里添加了两个测试接口,在前面ShiroConfig中已经对/login接口进行了权限的开放以及对 /user接口配置为需要认证。测试过程中,如果直接访问 /user接口是会跳转到 /login接口的,只有在/login接口访问成功之后,才可以成功访问 /user接口。
这个只是按照Shiro的许多默认配置实现的有状态的认证,在实际使用过程中可以使用JWT配合Shiro来实现无状态的认证。
网友评论