跟着狂神,编程不良人学的(仅供个人学习)
链接视频:
https://www.bilibili.com/video/BV1uz4y197Zm
https://www.bilibili.com/video/BV1PE411i7CV?p=38
springboot整合shiro(这个是很久以前看狂神入门的)
shiro
shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
Java的shiro应用
shiro配置文件
.ini 结尾文件,可以写一些复杂的数据格式
其实整合springboot之后用不到了,这个用来学习shiro书写我们系统中相关权限数据
[users]
# 要用键等于值的方式
xiaochen=123
zhangsan=123456
lisi=789
@Test
public void shiroTest(){
//1.创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.SecurityUtils 全局安全工具类,设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen","123");
try{
System.out.println(subject.isAuthenticated());//认证状态
subject.login(token);
System.out.println(subject.isAuthenticated());
//两个常见异常
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
subject.login(token);
}
结果
false
2021-11-07 14:39:58.099 INFO 16748 --- [ main] a.s.s.m.AbstractValidatingSessionManager : Enabling session validation scheduler...
true
自定义Realm
package com.kaka.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
//自定义realm,必须继承AuthorizingRealm
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在token中获取用户名
String principal = (String)authenticationToken.getPrincipal();
System.out.println(principal);
//获取用户名,比如从数据库中,这里方便理解,就定死
if ("xiaochen".equals(principal)){
//三个参数 1.用户名 2.密码 3.realm this.getName
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"123",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
test
@Test
public void shiroTest(){
//1.创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm(new CustomerRealm());
//3.SecurityUtils 全局安全工具类,设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen","123");
try{
subject.login(token);
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
}
MD5+盐值
为了安全起见,就需要使用MD5和盐值
package com.kaka.shiro;
import org.apache.shiro.crypto.hash.Md5Hash;
public class TestShiroMD5 {
public static void main(String[] args) {
//这样的使用并没有加密,不要使用set方法,而是构造
Md5Hash md5 = new Md5Hash();
md5.setBytes("123".getBytes());
System.out.println(md5.toHex());
//使用MD5
Md5Hash md5Hash = new Md5Hash("123");
System.out.println(md5Hash.toHex());
//使用MD5 + salt
Md5Hash md5Hash1 = new Md5Hash("123" ,"XOSJDH*764");
System.out.println(md5Hash1.toHex());
//使用md5 + salt + 散列
Md5Hash md5Hash2 = new Md5Hash("123" ,"XOSJDH*764" , 1024);
System.out.println(md5Hash2.toHex());
}
}
运行结果
313233
202cb962ac59075b964b07152d234b70
aacae0d20ffb1ca3625eb938e7ff533f
87a62690b6798a58023b4c872f566ed0
Process finished with exit code 0
hash凭证匹配
自定义的realm中写成(使用的普通的md5加密)
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"202cb962ac59075b964b07152d234b70",this.getName());
设置realm即可
CustomerRealm customerRealm = new CustomerRealm();
//设置hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//2.给安全管理器设置realm
securityManager.setRealm(customerRealm);
如果是加了盐的密码呢?(无需设置realm)
//四个参数 1.用户名 2.加了盐的密码 3.盐 4.realm this.getName
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "aacae0d20ffb1ca3625eb938e7ff533f", ByteSource.Util.bytes("XOSJDH*764"),this.getName());
如果散列了1024次了?
那就在realm设置
CustomerRealm customerRealm = new CustomerRealm();
//设置hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(1024);//加上这句即可
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//2.给安全管理器设置realm
securityManager.setRealm(customerRealm);
授权
realm
package com.kaka.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.apache.shiro.util.ByteSource;
import java.util.Arrays;
//自定义realm,必须继承AuthorizingRealm
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("身份信息" + principalCollection.getPrimaryPrincipal());
//根据身份信息 用户名 获取当前权限 此时的身份信息 xiaochen
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");//给予admin权限
simpleAuthorizationInfo.addRole("user");//给予user权限
simpleAuthorizationInfo.addStringPermission("user:*:01");//对01 user 具有所有权限
return simpleAuthorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在token中获取用户名
String principal = (String)authenticationToken.getPrincipal();
System.out.println(principal);
//获取用户名,比如从数据库中,这里方便理解,就定死
if ("xiaochen".equals(principal)){
//四个参数 1.用户名 2.加了盐的密码 3.盐 4.realm this.getName
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "87a62690b6798a58023b4c872f566ed0", ByteSource.Util.bytes("XOSJDH*764"),this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
test
package com.kaka.shiro;
import com.kaka.config.CustomerRealm;
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.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
public class Test {
public static void main(String[] args) {
//1.创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
CustomerRealm customerRealm = new CustomerRealm();
//设置hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//2.给安全管理器设置realm
securityManager.setRealm(customerRealm);
//3.SecurityUtils 全局安全工具类,设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen","123");
try{
subject.login(token);
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
//认证用户进行授权
if(subject.isAuthenticated()){
//1.基于角色权限控制
System.out.println(subject.hasRole("admin"));
System.out.println("权限" + subject.isPermitted("user:update:01"));
System.out.println("权限" + subject.isPermitted("user:update:02"));//只对01可行
}
}
}
运行结果
xiaochen
身份信息xiaochen
true
身份信息xiaochen
权限true
身份信息xiaochen
权限false
Process finished with exit code 0
Spring Boot 整合shiro
思路
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1jbcgsC-1636792239922)(D:\blog\source_posts\shiro\image-20211108101255980.png)]
依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.1</version>
</dependency>
一个关于realm注入的问题
当你写的时候,会发现Realm不能注入进去,具体原因不明,
@Bean
public DefaultSecurityManager getDefaultSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
@Bean
public Realm getRealm(){
//UserRealm userRealm = new UserRealm();
return new UserRealm();
}
问题
Description:
No bean of type 'org.apache.shiro.realm.Realm' found.
解决方案
public DefaultSecurityManager getDefaultSecurityManager(@Qualifier("getRealm") Realm realm){
}
@Bean(name = "getRealm")
public Realm getRealm(){
}
简单使用登录,注销(开发个人博客测试时,自己写的)
controller层
package com.kaka.site.controller;
import com.kaka.site.service.LoginService;
import com.kaka.site.vo.JsonResult;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@RequestMapping("/back")
@Controller
public class BackController {
@Autowired
private LoginService LoginService;
@RequestMapping("/toLogin")
public String toLogin(){
return "back/login";
}
@RequestMapping("login/{username}/{password}")
@ResponseBody
public JsonResult login(@PathVariable String username, @PathVariable String password){
return LoginService.login(username,password);
}
@RequestMapping("/toIndex")
public String toIndex(){
return "back/index";
}
@RequestMapping("/logout")
public String logout(){
SecurityUtils.getSubject().logout();
return "redirect:/back/toLogin";
}
}
service
package com.kaka.site.service.impl;
import com.kaka.site.service.LoginService;
import com.kaka.site.vo.JsonResult;
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.Service;
@Service
public class LoginServiceImpl implements LoginService {
@Override
public JsonResult login(String userName, String password) {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(userName, password));
return new JsonResult(200,"登陆成功");
} catch (UnknownAccountException e){
return new JsonResult(10000,"用户名错误");
}catch (IncorrectCredentialsException e){
return new JsonResult(10001,"密码错误");
}
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User findByUserName(String userName) {
return userMapper.selectOne(new QueryWrapper<User>().eq("user_name",userName));
}
}
shiroConfig
package com.kaka.site.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.kaka.site.shiro.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.ShiroFilter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//创建ShiroFilter
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//设置过滤器
Map<String,String> filterMap = new HashMap<>();
filterMap.put("/back/*","authc");
filterMap.put("/back/login","anon");
//设置登录请求
shiroFilterFactoryBean.setLoginUrl("/back/toLogin");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
//要用DefaultSecurityManager,他里面有web容器
@Bean
public DefaultSecurityManager getDefaultSecurityManager(@Qualifier("getRealm") Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
@Bean(name = "getRealm")
public Realm getRealm(){
UserRealm userRealm = new UserRealm();
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(1024);
userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return userRealm;
}
//配式shiro整合thymeleaf
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
realm
package com.kaka.site.shiro;
import com.kaka.site.entity.User;
import com.kaka.site.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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userService.findByUserName(token.getUsername());
if (!ObjectUtils.isEmpty(user)){
//四个参数 1.用户名 2.加了盐的密码 3.盐 4.realm this.getName
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
user.getUserName(),
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),
this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
springboot整合shiro(这个是很久以前看狂神入门的)
1. 环境准备
shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
使用mabatisplus整合
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
配置文件
server:
port: 8080
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
编写controller和index.html
package com.kaka.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UserController {
@RequestMapping({"/index", "/"})
public String toIndex(Model model) {
model.addAttribute("msg", "Hello Shiro");
return "index";
}
}
<!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>
</body>
</html>
2.shiro配置
在主程序同级目录下新建config包,其中新建ShiroConfig配置类
其中需要配置三大对象并将其注入到spring容器中:
- realm对象:可看作安全实体的数据源,该对象需要自定义,继承AuthorizingRealm类
- DefaultWebSecurityManager对象:默认安全管理器实体
- ShiroFilterFactoryBean对象:Shiro过滤工厂实体
- 首先编写自定义的realm类
UserRealm
,只需要继承AuthorizingRealm
类,重写其认证和授权的方法
package com.kaka.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
return null;
}
}
编写shiro的配置类ShiroConfig,其中声明三个对象:
- realm安全实体数据源:用我们自定义的UserRealm类来创建
- DefaultWebSecurityManager默认安全管理器:该对象需要关联realm对象,在方法参数中传入realm对象的参数,用@Qualifier指定需要的realm实现类UserRealm方法名即可
- ShiroFilterFactoryBeanshiro过滤工厂对象:该对象需要关联SecurityManager对象,同样在参数中传入该对象的参数,用@Qualifier指定需要的实现类方法名
package com.kaka.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;
@Configuration
public class ShiroConfig {
//创建realm,使用我们自定义的
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//配式shiro整合thymeleaf
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
3.Shiro实现登录拦截
编写页面以及controller,add.html和update.html放在user目录下
index.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>
<a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>
</body>
</html>
add.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>add</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
update.html
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <title>add</title> </head> <body> <h1>update</h1> </body></html>
controller
@RequestMapping("/user/add")public String add() { return "user/add";}@RequestMapping("/user/update")public String update() { return "user/update";}
在shiro配置类中,我们创建了三个对象,要实现登录拦截功能,就要用到shiro过滤工厂对象
我们在配置类
ShiroConfig
的shiroFilterFactoryBean()
方法中添加shiro的拦截器,实现登录过滤的功能
//ShiroFilterFactoryBean@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); //添加shiro内置的过滤器 /* anon:无需认证就可以访问 authc:必须认证了才能访问 user:必须拥有记住我功能才能使用 perms:拥有对某个资源的权限才能访问 role:拥有某个角色权限才能访问 */ Map<String, String> filterMap = new LinkedHashMap<>();//链式 filterMap.put("/user/add", "anon"); filterMap.put("/user/update", "authc"); //filterMap.put("/user/*", "authc");支持通配符 bean.setFilterChainDefinitionMap(filterMap);//参数为map类型 //设置登录的请求 bean.setLoginUrl("/login"); return bean;}
此时,点击
add
可以正常访问,点击update
无法正常访问,这是因为拦截器的作用,/user/add
请求无需认证就可以访问,但是/user/authc
请求需要认证才能访问
4.编写拦截后的登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>登录</h1>
<form action="/login">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p><input type="submit"></p>
</form>
</body>
</html>
点击update就会跳转到login了
5.Shiro实现用户认证
上述创建Realm对象中,我们继承了AuthorizingRealm类,重写了其两个方法,shiro实现用户认证的功能,也就是在其中的认证方法doGetAuthenticationInfo中完成的
实现用户认证有几个步骤:
- 获取当前用户
- 封装用户信息生成token令牌
- 执行登录操作(可以自定义捕获异常)
- 我们需要传递username和password
添加tologin方法
@RequestMapping("/toLogin")
public String toLogin(String username, String password, Model model) {
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户信息生成token令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//执行登录操作,可以自定义捕获异常
try {
subject.login(token);
return "index";//登录成功返回首页
} catch (UnknownAccountException e) {
model.addAttribute("msg", "用户名不存在");
return "login";//用户名错误回到登录页面
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg", "密码不正确");
return "login";//证书
}
}
此时随便输入账号密码,发现控制台执行了认证方法
因此我们需要在该方法中添加认证用户信息代码
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//伪造正确用户名和密码
String username = "kaka";
String password = "123";
//用户名认证
if (!token.getUsername().equals(username))
return null;//只需要return null,就会自动抛出UnknownAccountException异常
//密码认证,涉及到安全问题,shiro自动完成
return new SimpleAuthenticationInfo("", password, "kaka");
}
6.通过数据库来获取数据
没有写service层,因为业务太简单,不需要写
//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthorizationInfo"); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //伪造正确用户名和密码 User user = userMapper.selectOne(new QueryWrapper<User>().eq("name", token.getUsername())); System.out.println(user); //用户名认证 if (user == null) return null;//只需要return null,就会自动抛出UnknownAccountException异常 //密码认证,涉及到安全问题,shiro自动完成 //第三个参数是盐值 return new SimpleAuthenticationInfo("", user.getPwd(), "kaka");}
现在就可以登录数据库的数据了
7.Shiro请求授权实现
要实现登录拦截功能,同样通过shiro过滤工厂设置权限:
在shiro配置类ShiroConfig中的shiroFilterFactoryBean()方法中添加相关代码实现请求授权
//设置授权,只有user:add权限的才能请求/user/add
filterMap.put("/user/add", "perms[user:add]");
//设置授权,只有user:update权限的才能请求/user/update
filterMap.put("/user/update", "perms[user:update]");
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro内置的过滤器
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user:必须拥有记住我功能才能使用
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();//链式
filterMap.put("/user/add", "perms[user:add]");//设置授权,只有user:add权限的才能请求/user/add
filterMap.put("/user/update", "perms[user:update]");
bean.setFilterChainDefinitionMap(filterMap);//参数为map类型
//设置登录的请求
bean.setLoginUrl("/login");
return bean;
}
我们编写一个未授权页面,当没有权限时,跳转到该页面,
@RequestMapping("/unauthorized")
@ResponseBody
public String unauthorized() {
return "未授权,无法访问此页面";
}
然后同样在shiroFilterFactoryBean方法中设置未授权页面的请求
//设置未授权页面的请求bean.setUnauthorizedUrl("/unauthorized");
数据库中有perms权限,即获得权限
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获取当前subject
Subject subject = SecurityUtils.getSubject();
//通过subject获取当前user,下面方法参数改成user
User CurrentUser = (User) subject.getPrincipal();
//设置当前user的权限(从数据库中读取)
info.addStringPermission(CurrentUser.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//伪造正确用户名和密码
User user = userMapper.selectOne(new QueryWrapper<User>().eq("name", token.getUsername()));
//用户名认证
if (user == null)
return null;//只需要return null,就会自动抛出UnknownAccountException异常
//密码认证,涉及到安全问题,shiro自动完成
return new SimpleAuthenticationInfo(user, user.getPwd(), "");
}
此时不同的用户就有不同的权限
8.Shiro整合thymeleaf
如果我们想实现在首页,拥有对应权限的用户只显示对应的超链接?
这时候就可以通过Thymeleaf来完成,
- 导入依赖
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- 进行配置
在shiro配置类
ShiroConfig
中编写对应的配置
//配式shiro整合thymeleaf
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
- 修改index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<p>
<a th:href="@{/login}">登录</a>
</p>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
如果我们还想实现如果登录成功就不显示登录链接了呢?
我们可以用session来完成,当用户登录后,将其session存入,然后前端判断session是否为空来显示
- 添加session
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//伪造正确用户名和密码
User user = userMapper.selectOne(new QueryWrapper<User>().eq("name", token.getUsername()));
//用户名认证
if (user == null)
return null;//只需要return null,就会自动抛出UnknownAccountException异常
//将用户信息存入session
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("loginUser",user);
//密码认证,涉及到安全问题,shiro自动完成
return new SimpleAuthenticationInfo(user, user.getPwd(), "");
}
- 修改index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<p th:if="${session.loginUser}==null">
<a th:href="@{/login}">登录</a>
</p>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>