首先搭建一个简单的测试环境
- 搭建一个springboot 项目
- 引入依赖,主要是web starter shiro(安全认证) 和thymeleaf(简单的页面构建),这里不做数据层的查询,所以不引入数据库相关的。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--引入shrio-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
</dependencies>
- 页面的构建,目录如下:
首页入口
helloword.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<hr>
<p><a href="add">add</a> | <a href="update">update</a></p>
</body>
</html>
点击add/update 接口的调用,将在这两个接口加入认证
add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>添加</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
</body>
</html>
update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>更新</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
</body>
</html>
点击接口如果没有登录,将跳转到登录页面
login/loginuser.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<p style="color: red" th:text="${msg}"></p>
<form action="/login/loginInfo">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<input type="submit">
</form>
</body>
</html>
- 写一个controller,给页面提供接口
@Controller
@RequestMapping("/login")
public class LoginController {
@GetMapping("/index")
public String gotoIndex(Model model) {
model.addAttribute("msg","hello shiro");
return "helloword";
}
@GetMapping("/add")
public String add(Model model, HttpServletRequest request) {
model.addAttribute("msg","添加成功!!!");
HttpSession session = request.getSession();
Object user_session = session.getAttribute("USER_SESSION");
return "add";
}
@GetMapping("/update")
public String update(Model model) {
model.addAttribute("msg","更新成功!!!");
return "update";
}
@GetMapping("/tologin")
public String tologin(Model model) {
return "login/loginuser";
}
}
shiro讲解
在概念层,Shiro 架构包含三个主要的理念:Subject,SecurityManager和 Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。
-
Subject:当前用户,Subject
可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。 -
SecurityManager:管理所有Subject,SecurityManager 是 Shiro
架构的核心,配合内部安全组件共同组成安全伞。 Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 -
Realm:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro
的时候,你必须指定至少一个Realm来实现认证(authentication)和/或授权(authorization)。
编写Shiro配置类
首先要有一个配置类 ShiroConfig,里面提供一些bean,ShiroFilterFactoryBean,DefaultWebSecurityManager,Realm;我们先搭建一个Realm,这个类主要做认证和授权
/**
* @author ojj
* @title: MyRealm
* @projectName test
* @description:
* @date 2021/12/31 10:17
*/
public class MyRealm extends AuthorizingRealm {
/**
* 授权 角色
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
return null;
}
/**
* 认证 登录
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
String name = (String)authenticationToken.getPrincipal();
System.out.println("用户:"+name);
//模拟从数据库中获取用户登录信息
String username = "sb";
String password = "123456";
if (!username.equals(name)){
return null;
}
return new SimpleAuthenticationInfo(name,password,"");
}
}
ShiroConfig
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean()
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//不拦截的接口
filterChainDefinitionMap.put("/login/loginInfo","anon");
filterChainDefinitionMap.put("/login/index","anon");
//需要认证的接口
//filterChainDefinitionMap.put("/login/add","authc");
//filterChainDefinitionMap.put("/login/update","authc");
//其余接口一律拦截
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//认证不通过 跳转登录页面
shiroFilterFactoryBean.setLoginUrl("/login/tologin");
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager
@Bean("securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
//ShiroRealm
@Bean("realm")
public Realm getRealm(){
return new MyRealm();
}
}
在Controller添加一个登录的接口
@GetMapping("/loginInfo")
public String login(String username, String password,Model model) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
//登录
subject.login(token);
return "helloword";
}catch (UnknownAccountException e) {
model.addAttribute("msg","用户不存在");
return "login/loginuser";
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg","密码错误");
return "login/loginuser";
}
}
测试
点add,没登录的情况下,会跳转到登录页面
登录成功后,会返回一个session,之后点击add/update可以正常访问了
添加角色权限
比如说某个接口需要有经理级别才能使用,这时需要改造Realm中有另一个方法doGetAuthorizationInfo。
/**
* @author ojj
* @title: MyRealm
* @projectName test
* @description:
* @date 2021/12/31 10:17
*/
public class MyRealm extends AuthorizingRealm {
/**
* 授权 角色
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
//获取用户名字
String name = (String) principalCollection.getPrimaryPrincipal();
//使用name从数据库获取用户角色和权限
String role = "admin";
String permission = "test";
//添加角色 权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole(role);
simpleAuthorizationInfo.addStringPermission(permission);
return simpleAuthorizationInfo;
}
/**
* 认证 登录
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
String name = (String)authenticationToken.getPrincipal();
System.out.println("用户:"+name);
//模拟从数据库中获取用户登录信息
String username = "sb";
String password = "123456";
if (!username.equals(name)){
return null;
}
//name 可以上面的授权方法获取
//password 用来比对密码
//realmName
return new SimpleAuthenticationInfo(name,password,"");
}
}
然后哪些需要角色或权限的接口上加上注解@RequiresRoles @RequiresPermissions,shiro会自动去比对是否符合访问要求,如下 @RequiresRoles(“admin2”) 是访问不成功的,因为用户绑定了admin(伪代码写死),可以使用@RequiresPermissions(“test:update”)来设置多种权限。
@RequiresRoles("admin2")
@GetMapping("/add")
public String add(Model model, HttpServletRequest request) {
model.addAttribute("msg","添加成功!!!");
HttpSession session = request.getSession();
Object user_session = session.getAttribute("USER_SESSION");
return "add";
}
@RequiresPermissions("test")
@GetMapping("/update")
public String update(Model model) {
model.addAttribute("msg","更新成功!!!");
return "update";
}