参考了b站的springboot视频。
1、shiro简介
它是阿帕奇的java安全框架。包含了三个主要对象:subject(当前用户)、SecurityManager(安全管理)、Realm(数据相关)。
Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。其基本功能点如下图所示:
shiro详细介绍
2、demo结构:
3、使用了springboot、mybatis、druid etc,实现登录控制,用户认证、请求授权(本文没有实现授权)等。
4、首先搭建环境:
java配置类Shiroconfig和自定义的Realm对象:UserRealm。它们之间的联系建立再Realm这个对象上。
Shiroconfig:(可以配置登录拦截和授权)
package com.example.shiro_pht.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class Shiroconfig {
//shiro的三大对象:suject securityManager realm
//shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getsfb(@Qualifier("getdsm") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//1、实现登录拦截
//添加shiro的内置过滤器
/*
anon:无需认证就能访问
authc:必须认证
user:必须拥有remember me功能才能
perms:拥有某个资源的权限才能使用
role:拥有角色权限才能访问
必须用到setFilterChainDefinitionMap,设置过滤器链,打开其源码,其参数为一个map,用于添加
*/
Map<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
//权限操作(顺序要注意)
//filterChainDefinitionMap.put("/userpage/add","perms[userpage:add]"); //只有带了user:add的用户才有权限访问
//filterChainDefinitionMap.put("/userpage/update","perms[userpage:update]");
//登录认证拦截
filterChainDefinitionMap.put("/userpage/add","anon"); //这个路径无需认证
filterChainDefinitionMap.put("/userpage/*","authc"); //即这个路径必须认证,否则跳转到错误页面
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//如果没有权限则跳转到一个页面(这里我们跳转到登录页面)
shiroFilterFactoryBean.setLoginUrl("/tologin");
//跳转到未授权页面
//shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getdsm(@Qualifier("realms") UserRealm userRealm){
DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
//关联realm
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
//realm对象,使用自定义的realm类对象
@Bean
public UserRealm realms(){
return new UserRealm();
}
}
自定义Realm对象,UserRealm(认证部分连接了service层,获取数据库中的用户名和密码),其中的token,它与Hellocontroller中的token是互通的(在框架环境下),直接调用:
package com.example.shiro_pht.config;
import com.example.shiro_pht.Pojo.User;
import com.example.shiro_pht.service.Userservice;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
//自定义realm,继承AuthorizingRealm即可
public class UserRealm extends AuthorizingRealm {
@Autowired
Userservice userservice;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权操作!");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证操作!");
//upt即token,它与Hellocontroller中的token是互通的,直接调用!!!
//这里很重要!!!
UsernamePasswordToken upt=(UsernamePasswordToken)authenticationToken;
//连接数据库
User user=userservice.queryuser(upt.getUsername());
System.out.println(upt);
System.out.println(upt.getUsername());
if(user==null){
return null; //抛出错误
}
return new SimpleAuthenticationInfo("",user.getPassword(),"");
}
}
5、Controller层:
package com.example.shiro_pht.Controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class Hellocontroller {
@RequestMapping({"/", "/index"})
public String hello(Model model) {
model.addAttribute("msg", "hello");
return "index";
}
@RequestMapping("/userpage/add")
public String add() {
return "userpage/add";
}
@RequestMapping("/userpage/update")
public String update() {
return "userpage/update";
}
@RequestMapping("/tologin")
public String tologin() {
return "login";
}
@RequestMapping("/login")
public String login(@RequestParam("name") String name,@RequestParam("password") String password, Model model) {
System.out.println(name);
System.out.println(password);
//使用shiro获取当前用户
Subject userNow = SecurityUtils.getSubject();
//获取令牌
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
try {
//执行登录的方法,这步封装很复杂
userNow.login(token);
return "index";
} catch (UnknownAccountException e) {
model.addAttribute("msg", "用户名错误");
return "login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg", "密码错误");
return "login";
}
}
/*
@RequestMapping("/noauth")
@ResponseBody
public String unauth(){
return "没有权限";
}
*/
}
6、mapper层,Usermapper:
package com.example.shiro_pht.mapper;
import com.example.shiro_pht.Pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Repository
@Mapper
public interface Usermapper {
public User queryuser(String name);
}
7、实体类:
package com.example.shiro_pht.Pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private String password;
}
8、由于功能简单,service层接口与mapper层方法相同,实现类Userserviceimp:
package com.example.shiro_pht.service;
import com.example.shiro_pht.Pojo.User;
import com.example.shiro_pht.mapper.Usermapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class Userserviceimp implements Userservice{
@Autowired
Usermapper usermapper;
@Override
public User queryuser(String name) {
User user=usermapper.queryuser(name);
return user;
}
}
9、resources:
mapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.shiro_pht.mapper.Usermapper">
<select id="queryuser" resultType="user">
select * from accountlist where name=#{name}
</select>
</mapper>
index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<p th:text="${msg}"></p>
This is the first page!
<a href="/userpage/add">add</a><br>
<a href="/userpage/update">update</a>
</body>
</html>
login.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}" style="color:#f60202"></p>
<form th:action="@{/login}">
username:<input type="text" name="name"><br>
password:<input type="text" name="password"><br>
<input type="submit" value="submit">
<input type="reset" value="reset">
</form>
</body>
</html>
application.yaml:
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/majinbuu1?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 500
filters: stat,wall,log4j
maxPoolPreparedAtatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: durid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.shiro_pht.Pojo
add.html与update.html略。就一句body。
10、实验报错与结果:
报错:
1、org.apache.shiro.authc.UsernamePasswordToken - null报错:
通过打印输出测试,最终发现错误为前端传值的名字和后端接收时不匹配。(username----->name)
2、授权页面无法跳转,按照网上的说法,调整了Shiroconfig中授权和拦截的顺序,仍然不行,貌似授权页面无法跳转,因为是懒加载blabla,未解决…
结果:
数据库中的所有账户密码都能登录,且正确的实现了拦截、认证等。
这只是个入门级别的demo,记录下学习过程,以后有机会再深入学习。