springboot整合shiro核心步骤:①导入启动器;②自定义realm进行认证和授权;③创建配置类,将自定义realm、securityManager交由ioc管理
shiro为什么没有比对账号?
因为在认证时在查数据库查到了就代表账号一定存在,当存在时会将密码交给shiro,如果密码比对成功就代表认证通过。这当中账号只是起到查询密码的作用,真正比对还是密码之间的比对。账户不存在,自然也就查不到数据,自然也就没必要比对
shiro主要改变了登录的书写,对其他模块的影响主要是他会拦截其它模块的资源,一不小心被拦截了,连报错都不知道为什么
步骤一:在pom.xml文件中导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!-- druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!-- freemaker启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--aop的启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- shiro ,shiro和quartz不使用时不能放开,不然会报错-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0</version>
</dependency>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
步骤二:创建自定义的realm
该案例将该类命名为:MyRealm
package com.qf.springbootcombineshiro.realm;
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 java.util.HashSet;
import java.util.Set;
public class MyRealm extends AuthorizingRealm{
//授权。第一大类:hasRoles,hasPermission,chekRoles,checkPermission;第二种触发:过滤连;第三种触发:freemarker的shiro标签触发等方式触发该授权方法,has和checked区别在于has系列返回的是boolean值,check系列是直接抛出异常,可用controllerAdvice全局异常进行处理,用于授权和赋予角色
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Object username = principalCollection.getPrimaryPrincipal();//获取用户名
System.out.println("授权中的username = " + username);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Set<String> roles=new HashSet<>();//用于装从数据库查询出来的认证通过的用户对应的角色.用set集合一方面是为了去重,另一方面是因为SimpleAuthorizationInfo的设置角色属性只能用set集合封装
Set<String> permissions=new HashSet<>();//用于装从数据库查询出来的认证通过的用户对应的权限
//这里角色和权限时假数据,一般这里是根据用户名调用service从数据库查询出来的真实角色和权限
roles.add("老板");//登录就拥有这两个角色和这两个权限了
roles.add("女秘书");
permissions.add("开会");
permissions.add("管老板");
//将查询出来的用户对应的角色和权限封装到SimpleAuthorizationInfo传给shiro进行权限管理
simpleAuthorizationInfo.addRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
//返回封装了用户对应的真实角色和权限的simpleAuthorizationInfo,交给shiro管理
return simpleAuthorizationInfo;
}
//认证。login方法触发。用于根据用户名查询用户真实信息。如果账号出错一定是这里的认真有问题
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取用户注入的信息
String username=(String)authenticationToken.getPrincipal();//获取用户从页面输入的用户名
System.out.println("用户输入的用户名 = " + username);//因为此处没查询数据库,无法根据账号查询出密码,因此,账号输什么都可已通过,只要密码一致就可以登录了
//当结合数据库查时,用if判断账号在数据库中是否存在,不存在返回null,代表认证失败,存在用SimpleAuthenticationInfo封装信息并返回simpleAuthenticationInfo对象,代表存在账号,然后将密码交给shiro框架进行比对
//结果返回
SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo(username,"1111","myrealm");//第一个参数通常是根据用户名从数据库查询出来的封装了用户真实信息的对象,controller层通过subject.getPrinciple()可以获取到,但为了方便,这里写的假数据。第二个是根据用户名查询的用户的真实密码,用于shiro底层做真密码和用户输入的密码的比对,这里也是制作的假数据
return simpleAuthenticationInfo;
}
}
步骤三:创建配置类,将realm、securityManager交给ioc管理
该案例将该配置类命名为:ShiroConfiguration
package com.qf.springbootcombineshiro.configuration;
//这个shiro配置能主要做两件事:①将自定义的realm交给ioc管理;②声明过滤连,告诉shiro哪些为匿名资源可以放行,哪些不能放行
import com.qf.springbootcombineshiro.realm.MyRealm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//这就像是ssm中xml配置文件中那样,将SecurityManager、realm、交给ioc容器管理
@Configuration
public class ShiroConfiguration {
//给shiro声明自定义的realm
@Bean
public MyRealm createRealm(){//这个方法会将返回值即自定义的realm交给ioc管理
MyRealm myRealm=new MyRealm();
return myRealm;
}
//创建securityManager对象,将自定义realm封装进securityManager
@Bean//方法上放bean代表该方法交由ioc管理
public DefaultWebSecurityManager createSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(createRealm());//将自定义的realm将给securityManager传递给shiro
return securityManager;
}
//固定方法,直接复制即可。用于控制注解权限一定生效
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setUsePrefix(true);
return creator;
}
//过滤连声明,告诉shiro放行哪些资源和路径
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
//创建过滤连
DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
//创建过滤规则,告诉shiro哪些资源放行,哪些需要认证和具有对应权限才能放行
// chain.addPathDefinition("/js/**","anon"); //静态资源放行写法
chain.addPathDefinition("/login","anon");//将/login请求地址设置为匿名资源,即不用登录就可以进行访问.login一定要设置为匿名资源,因为shiro默认拦截后跳转去login路径,因此跳去登录页面的请求地址最好就是/login
chain.addPathDefinition("/deal","anon");
chain.addPathDefinition("/home","authc");//将/home请求地址设置为必须要登录后(即认证通过后)才能访问,
chain.addPathDefinition("/logout","logout");//将/logout设置为shiro自带的logout,这样就不用写退出登录了,直接调用/logout请求地址即可,它不仅会退出还会清除用户登录信息
return chain;
}
}
步骤四:创建触发认证的controller类,也就是登录请求触发的方法
该案例命名为:ShiroController
package com.qf.springbootcombineshiro.controller;
import com.qf.springbootcombineshiro.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
@Controller
public class ShiroController {
//跳转登录首页
@RequestMapping("/login")
public String jumpLogin(){
return "login";
}
//获取登录页面用户输入的账号、密码,调用shiro的方法,触发自定义realm的认证和授权
@RequestMapping("/deal")
public String dealLogin(User user, HttpSession session){//由于自定义realm中我们设置的假数据的账号为随便哪个值,密码为1111.因此真实的用户名和面就是这两个
Subject subject = SecurityUtils.getSubject();
System.out.println(user.getUsername()+ user.getPassword());
//将用户输入的账户名和密码封装进UsernamePasswordToken
UsernamePasswordToken usernamePasswordToken
= new UsernamePasswordToken(user.getUsername(), user.getPassword());
//由于认证失败会报异常,因此用trycatch捕获
try{ //当一直跳转到登录页 的时候记得将这个trycatch删掉再测试,因为这里捕获的异常范围太大了,自定义realm中无法报错
subject.login(usernamePasswordToken);//触发自定义realm的认证方法
String username = (String)subject.getPrincipal();//这里获取的是自定义realm中SimpleAuthenticationInfo存的第一个参数,这第一个参数也可以是含有用户信息的一个对象
session.setAttribute("username",username);
// User user = (User)subject.getPrincipal();//可通过该方法获取到自定义realm认证方法那存的第一个参数
return "home";
}catch (Exception e){//因为这里捕获的异常范围很大,也可能捕获的并非用户名和密码错误,只要是自定义realm认证方法错误,这里也会捕获,比如userservice由于自定义realm加载顺序问题而导致注入失败发生的异常,这里也能捕获,导致执行异常处理的代码
return "login";
}
}
//跳去home页面
@RequestMapping("/home")
public String jumpHome(){
return "home";
}
}
步骤五:该案例用到的实体类
该案例命名为:User
package com.qf.springbootcombineshiro.entity;
import java.io.Serializable;
public class User implements Serializable {
private String username;
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
步骤七:该案例用到的springboot启动类
该案例该启动类命名为:SpringbootcombineshiroApplication
package com.qf.springbootcombineshiro;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootcombineshiroApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootcombineshiroApplication.class, args);
}
}
步骤八:配置application.yaml文件,配置数据库,设置controller页面跳转的前后缀等信息
#服务器端口号设置
server:
port: 8080
#数据库信息
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/shiroweb
driver-class-name: com.mysql.jdbc.Driver
#前后缀
freemarker:
prefix: /
suffix: .ftl
cache: false #缓存
content-type: text/html
resources:
#修改静态资源加载默认位置
static-locations: classpath:/static
servlet:
multipart:
#设置上传文件的大小
max-request-size: 1000MB
max-file-size: 500MB
#mybatis\u57FA\u672C
mybatis:
#设置加载mapper文件路径
mapper-locations: mapper/*.xml
# type-aliases-package: com.itqf.springbootssmfreemaker03.entity
# configuration:
# map-underscore-to-camel-case: true
# auto-mapping-behavior: full
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#shiro拦截器
shiro:
loginUrl: /login #登录地址
unauthorizedUrl: /403 #未授权跳往403.ftl这个模板文件
enabled: true #web项目的支持
#\u914D\u7F6Efreemaker
该案例用到的ftl页面:
第一个ftl页面: login.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<center>
<form action="/deal" method="post">
账号:<input type="text" name="username" > <br>
密码:<input type="password" name="password"> <br>
<button>登录</button>
</form>
</center>
</body>
</html>
第二个ftl页面: home.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<#--登录成功后显示下用户名-->
${username}
</body>
</html>
第三个ftl页面: 403.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
授权未通过
</body>
</html>
本案例框架结构图:
静态资源放行相对路径图: