目录
前言
一、Shiro介绍
1、Shiro基本功能图
2、Shiro工作原理
二、Shiro实现认证和授权
1、先导入相关的依赖
2、属性配置文件
3、dao层配置
4、实体类配置
5、Service层配置
6、Controller层配置
7、配置类
8、运行结果
Apache Shiro是一个功能强大且易于使用的Java安全框架,可执行身份验证、授权、加密和会话管理。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。本文使用Shiro整合Mybatis,利用Druid数据库连接池,实现用户认证和授权,并利用Swagger生成API开发文档。
可以根据官网或者github快速入门Shiro安全框架
Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;它是一个比较抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与其他组件进行交互,如果学习过 SpringMVC框架,你就可以把它看成前端控制器( DispatcherServlet );
Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.apache.shiro
shiro-spring
1.9.0
org.thymeleaf.extras
thymeleaf-extras-java8time
3.0.4.RELEASE
org.thymeleaf
thymeleaf-spring5
3.0.15.RELEASE
mysql
mysql-connector-java
8.0.28
log4j
log4j
1.2.17
com.alibaba
druid
1.2.8
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.2
io.springfox
springfox-boot-starter
3.0.0
主要是对数据库的连接、数据库连接池Druid、Mybatis、log4j进行配置
application.yaml:
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
type-aliases-package: com.study.pojo
mapper-locations: classpath:mapper/*.xml
log4j.properties:
#应用于控制台
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
UserMapper.java:
package com.study.mapper;
import com.study.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Repository
@Mapper
public interface UserMapper {
//通过名字查询用户
User queryUserByName(String name);
}
UserMapper.xml:
User.java:
package com.study.pojo;
import java.util.Collection;
public class User {
private Integer userid;
private String username;
private String password;
private String perms;
public User() {
}
public User(Integer userid, String username, String password,String perms) {
this.userid = userid;
this.username = username;
this.password = password;
this.perms=perms;
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
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;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
@Override
public String toString() {
return "User{" +
"userid=" + userid +
", username='" + username + '\'' +
", password='" + password + '\'' +
", perms='" + perms + '\'' +
'}';
}
}
UserService.java:
package com.study.service;
import com.study.pojo.User;
public interface UserService {
//通过名字查询用户
User queryUserByName(String name);
}
UserServiceImpl.java:
package com.study.service;
import com.study.mapper.UserMapper;
import com.study.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
package com.study.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.RequestMapping;
@Controller
public class MyController {
@RequestMapping({"/", "index"})
public String toIndex(Model model) {
model.addAttribute("msg", "hello,shiro");
return "index";
}
@RequestMapping("/user/character")
public String add() {
return "user/character";
}
@RequestMapping("/user/picture")
public String update() {
return "user/picture";
}
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
@RequestMapping("/login")
public String login(String username, String password, Model model) {
//获取当前的用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);//执行登录方法,如果没有异常就说明OK了
return "index";
} catch (UnknownAccountException e) { //用户名不存在
model.addAttribute("msg", "用户名错误");
return "login";
} catch (IncorrectCredentialsException e) { //密码不存在
model.addAttribute("msg", "密码错误");
return "login";
}
}
}
配置类包含对Druid数据库连接池的配置,Shiro登录拦截,Shiro授权及认证的配置,Swagger配置
DruidConfig:
package com.study.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
//后台监控功能
//因为SpringBoot内置Servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登录,账号密码配置
HashMap initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername", "admin");//登录key是固定的loginUsername
initParameters.put("loginPassword", "123456");
//允许谁可以访问
initParameters.put("allow", "");
//禁止谁能访问 initParameters.put("deny","192.168.1.1");
bean.setInitParameters(initParameters);//设置初始化参数
return bean;
}
//配置一个web监控的filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//可以过滤哪些请求呢?
HashMap initParameters = new HashMap<>();
bean.setInitParameters(initParameters);
//这些东西不进行统计
initParameters.put("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return bean;
}
}
ShiroConfig:
package com.study.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 {
//第三步:ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
* anon:无需认证就可以访问
* authc:必须认证了才可以访问
* user:必须拥有记住我功能才能用
* perms:拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
* */
//拦截
Map filterMap=new LinkedHashMap<>();
//授权,正常的情况下,没有授权会跳到未授权的页面
filterMap.put("/user/character","perms[user:character]");
filterMap.put("/user/picture","perms[user:picture]");
//filterMap.put("/user/add","authc");
//filterMap.put("/user/update","authc");
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/toLogin");
return bean;
}
//第二步:DefaultWebSecurityManager
@Bean(name ="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm对象
securityManager.setRealm(userRealm);
return securityManager;
}
//第一步:创建realm对象,需要自定义类
@Bean //将自定义的类放入ShiroConfig,并被Spring托管
public UserRealm userRealm(){
return new UserRealm();
}
}
UserRealm:
package com.study.config;
import com.study.pojo.User;
import com.study.service.UserServiceImpl;
import org.apache.shiro.SecurityUtils;
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.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserServiceImpl userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
User currentUsers = (User) subject.getPrincipal();//拿到User对象
//设置当前用户的权限
if (currentUsers.getPerms()!=null) {
info.addStringPermission(currentUsers.getPerms());
return info;
}
else {
return null;
}
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//用户名,密码,从数据库中取
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User user = userService.queryUserByName(userToken.getUsername());
if (user == null) { //不存在这个用户
return null;
}
//密码认证,shiro去做
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
}
SwaggerConfig:
package com.study.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.ArrayList;
@Configuration
@EnableOpenApi
@EnableWebMvc
public class SwaggerConfig {
@Bean //配置了swagger的Docket的bean实例
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
//.enable(false)
.select()
//RequestHandlerSelectors,配置要扫描接口的方式
/* basePackage指定扫描的包;
any();扫描全部;
none();都不扫描;
withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象
withMethodAnnotation:扫描方法上的注解
.paths()过滤什么路径
.enable()是否启用Swagger,默认是true,若为false,则Swagger不能访问,并且会有一个小表情出现
*/
.apis(RequestHandlerSelectors.basePackage("com.study.controller"))
//.paths(PathSelectors.ant("/study/**"))
.build();
}
//配置swagger信息:通过覆盖默认的apiInfo
private ApiInfo apiInfo() {
//配置作者的信息
Contact contact = new Contact("全村第二帅", "https://blog.csdn.net/qq_53860947?type=blog", "[email protected]");
return new ApiInfo("自定义api文档",
"码道万古如长夜",
"v3.0",
"https://blog.csdn.net/qq_53860947?type=blog",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
前端界面过多,这里不展示,可以借助前端模板完成
主界面:
当用户通过url地址栏进一个网站,能看见一些内容,但如果要点击这些内容,会弹出登录界面提示用户登录。当然不同用户能看到的内容不一样,比如当你充了钱,你肯定要享受更好的服务了,数据库应设置对应的权限表,当然这里为了方便,就增加了一个权限字段。
登录界面:
首先进行用户登录认证,如果登录名、密码和数据库的记录不一样,则会产生提示
授权界面:
比如用户:李五,权限只能看图片,当然他只能点击看图片了 ,点击看文字,会跳到自己定制的错误401(没有权限)页面了。
再比如用户:无名,他就拥有看文字的权限了
SQL日志监控:
根据所配置的Druid后台用户名及密码登录到Druid的后台,然后查看SQL监控情况、SQL防火墙防御情况等相关信息。
Swagger API 日志文档:
注: 源码已上传至资源,有意者自取:shiro安全框架整合Mybatis-Java文档类资源-CSDN下载