Spring Data
springboot不管是关联SQL或者是NoSQL数据库,都是以data的形势
整合JDBC
记得添加JDBC API和MySQL Driver的依赖,还有WEB依赖,如果在创建的时候没有添加,也可以在pom中添加,不过尽量不要以这种方式,web依赖最好在创建项目的时候添加,以免发生一些web相关的bug
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
-
使用idea关联数据库 -
进行数据库相关配置 spring:
datasource:
username: root
password: 数据库密码
url: jdbc:mysql://localhost:3306/count_db?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
-
自动装配dataSource,测试springBoot默认的数据源 package com.michilay;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
@SpringBootTest
class Springboot04DataApplicationTests {
private DataSource dataSource;
@Autowired
public Springboot04DataApplicationTests(DataSource dataSource) {
this.dataSource = dataSource;
}
@Test
void contextLoads() {
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
class com.zaxxer.hikari.HikariDataSource 2021-08-23 10:01:46.649 INFO 2752 — [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting… 2021-08-23 10:01:48.858 INFO 2752 — [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. HikariProxyConnection@1582330795 wrapping com.mysql.cj.jdbc.ConnectionImpl@3c4262d1 2021-08-23 10:01:48.965 INFO 2752 — [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated… 2021-08-23 10:01:48.973 INFO 2752 — [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
hikari是data默认数据源,和dbcp,速度最快 -
jar包下XXXXTemplate表示就是就是SpringBoot已经帮我们配置好的模板bean,CRUD方法封装在里面,直接拿过来用就可以 例如我们要使用JDBC的数据源,找到Maven下的spring-boot-autoconfigure-2.3.7.RELEASE.jar,进入org找到JDBC包,找到JdbcTemplateConfiguration类,可以看到以下代码 @Configuration(
proxyBeanMethods = false
)
@ConditionalOnMissingBean({JdbcOperations.class})
class JdbcTemplateConfiguration {
JdbcTemplateConfiguration() {
}
@Bean
@Primary
JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Template template = properties.getTemplate();
jdbcTemplate.setFetchSize(template.getFetchSize());
jdbcTemplate.setMaxRows(template.getMaxRows());
if (template.getQueryTimeout() != null) {
jdbcTemplate.setQueryTimeout((int)template.getQueryTimeout().getSeconds());
}
return jdbcTemplate;
}
}
我们可以直接使用这个Bean进行操作就可以了 -
使用原生JDBC借助springboot封装的方法进行crud的操作 package com.michilay.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class JdbcController {
@RequestMapping("/t1")
public String test(){
return "hello springboot";
}
@Autowired
JdbcTemplate jdbcTemplate;
@GetMapping("/userList")
public List<Map<String,Object>> userList(){
String sql = "select * from count_db.account";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@GetMapping("/addUser")
public String addUser(){
String sql = "insert into count_db.account(userId,userName,userPwd,money) values (2,'123123','123123',123.1)";
jdbcTemplate.update(sql);
return "add OK!";
}
@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable("id") int id){
String sql = "update count_db.account set userName =?,userPwd=?,money=? where userId=" + id;
Object[] objects = new Object[3];
objects[0] = "mcly111";
objects[1] = "mcly1111";
objects[2] = "123.123";
jdbcTemplate.update(sql,objects);
return "update OK!";
}
@GetMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id") int id){
String sql = "delete from count_db.account where userId = ?";
jdbcTemplate.update(sql,id);
return "delete OK!";
}
}
整合Druid
Druid介绍
druid是阿里巴巴开源平台上一个数据库链接池的实现,结合了C3P0、DBCP、PROXOOL等DB池的优点,并且加入了日志监控
Druid使用
-
导入Druid依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
-
配置数据源 # 数据源配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
-
测试查看数据源
class com.alibaba.druid.pool.DruidDataSource 2021-08-23 11:26:03.089 INFO 14580 — [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited com.mysql.cj.jdbc.ConnectionImpl@608bc8f8
-
配置Druid专属配置 spring:
datasource:
username: root
password: 数据库密码
url: jdbc:mysql://localhost:3306/count_db?serverTimezone=UTC&userUnicode=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: 6000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
userGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true.druid.stat.slowSqlMillis=500
-
编写Druid配置类 package com.michilay.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.context.properties.ConfigurationProperties;
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();
}
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
HashMap<String, String> initParameters = new HashMap<>();
initParameters.put("loginUsername","admin");
initParameters.put("loginPassword","123");
initParameters.put("allow","");
bean.setInitParameters(initParameters);
return bean;
}
}
-
运行服务器,浏览器输入
http://localhost:8080/druid/sql.html
整合Mybatis
-
创建springboot项目,导入mybatis、web、jdbc、mysql等依赖 <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
-
配置相关信息 # 应用名称
spring.application.name=springboot-05-mybatis
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/count_db?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=数据库密码
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mapper/*.xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.michilay.pojo
# 应用服务 WEB 访问端口
server.port=8080
-
编写实体类和对应的mapper接口 package com.michilay.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int userId;
private String userName;
private String userPwd;
private Double money;
}
package com.michilay.mapper;
import com.michilay.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface UserMapper {
List<User> findAll();
User findUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
-
在编写对应的service层和实现类 package com.michilay.service;
import com.michilay.pojo.User;
import java.util.List;
public interface UserService {
List<User> findAll();
User findUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
package com.michilay.service;
import com.michilay.mapper.UserMapper;
import com.michilay.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements UserService{
UserMapper userMapper;
@Autowired
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public List<User> findAll() {
return userMapper.findAll();
}
@Override
public User findUserById(int id) {
return userMapper.findUserById(id);
}
@Override
public int addUser(User user) {
return userMapper.addUser(user);
}
@Override
public int updateUser(User user) {
return userMapper.updateUser(user);
}
@Override
public int deleteUser(int id) {
return userMapper.deleteUser(id);
}
}
-
编写实现增删改查的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.michilay.mapper.UserMapper">
<select id="findAll" resultType="User">
select * from count_db.account
</select>
<select id="findUserById" resultType="User">
select * from count_db.account where userId=#{userId}
</select>
<insert id="addUser" parameterType="User">
insert into count_db.account(userName, userPwd, money) values (#{userName},#{userPwd},#{money})
</insert>
<update id="updateUser" parameterType="User">
update count_db.account set userName=#{userName},userPwd=#{userPwd},money=#{money} where userId=#{userId}
</update>
<delete id="deleteUser" parameterType="int">
delete from count_db.account where userId=#{userId}
</delete>
</mapper>
记得放在正确的路径下面 -
编写对应的controller层 package com.michilay.controller;
import com.michilay.pojo.User;
import com.michilay.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/findAll")
public List<User> findAll(){
List<User> all = userService.findAll();
return all;
}
@GetMapping("/addUser")
public String addUser(){
User user = new User();
user.setUserName("boot");
user.setUserPwd("213asd");
user.setMoney(123D);
userService.addUser(user);
return "添加成功";
}
@GetMapping("/updateUser")
public String updateUser(){
User user = new User();
user.setUserId(1);
user.setMoney(1231232131232D);
userService.updateUser(user);
return "修改成功";
}
@GetMapping("/deleteUser")
public String deleteUser(){
userService.deleteUser(1);
return "删除成功";
}
}
-
运行程序进行测试,查看是否对数据库修改有作用
SpringSecurity
spring security是针对spring项目的安全管理框架,是针对于底层的安全模块默认的技术选型,它可以实现web安全控制,可以进行认证和授权
它是一种非功能性需求,安全问题是在网站从底层设计开始到后期维护的过程中一直要考虑的
项目构建
主页可以进入登陆页面,也可以通过主页进入三个等级的共九个页面
-
控制层代码如下 package com.michilay.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RouterController {
@RequestMapping({"/", "/index"})
public String index(){
return "index";
}
@RequestMapping("toLogin")
public String login(){
return "views/login";
}
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id") int id){
return "views/level1/"+id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id") int id){
return "views/level2/"+id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id") int id){
return "views/level3/"+id;
}
}
用户认证和授权
-
导入SpringSecurity的依赖 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
-
写一个配置类继承WebSecurityConfigurerAdapter,并且加上@EnableWebSecurity注释表示被spring托管了 @EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}
-
更改方法体如下 @EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
}
}
-
重启服务器,再次打开主页面,当我们点进相关页面时,会显示type=Forbidden, status=403 报错 -
但是我们不要报错,如果没有权限的话要跳转的登录页面
http.formLogin();
这样在我们没有权限,点击level的时候就会跳转到一个login页面,这个页面是secruity的页面 -
那么,那些用户拥有这些等级呢,这里先用内存创建用户,再重写一个方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("michilay").password(new BCryptPasswordEncoder().encode("123")).roles("vip2","vip1")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");
}
注意:必须得用加密,不然登录会出现问题
注销和权限控制
- 写一个注销按钮,跳转到注销请求,这些请求都是spring security封装好的,我们也可以把登陆和security自带的登录页绑定
-
开启注销功能
http.formLogin();
http.logout().logoutSuccessUrl("/");
-
如果要开启权限显示的功能,需要导入thymeleaf和security整合包 <dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
-
在首页的上方导入命名空间 <html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
-
通过sec命名空间结合security判断是否登录
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/login}">
<i class="sign-in icon"></i> 登录
</a>
</div>
<div sec:authorize="isAuthenticated()" style="display:flex; flex-direction:row;">
<a class="item">
用户名:<span sec:authentication="name"></span>
等级:<span sec:authentication="principal.authorities"></span>
</a>
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
-
如果登出失败,关闭scrf功能
http.csrf().disable();
-
sec也可以通过权限来判断,根据结果是否显示 <div>
<div class="ui segment" style="text-align: center" sec:authorize="!isAuthenticated()">
<h4>您还没有登录,请登录</h4>
</div>
<div class="ui three column stackable grid">
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
首页定制以及记住我功能
记住我的功能比较简单
配置自定义登陆页面
-
配置跳转到登陆页
http.formLogin().loginPage("/toLogin");
这里使用自己的登陆页面,toLogin是已经写好的登录跳转请求 -
写一个登陆页面 <form th:action="@{/login}" method="post">
<div class="field">
<label>Username</label>
<div class="ui left icon input">
<input type="text" placeholder="Username" name="username">
<i class="user icon"></i>
</div>
</div>
<div class="field">
<label>Password</label>
<div class="ui left icon input">
<input type="password" name="password">
<i class="lock icon"></i>
</div>
</div>
<div class="field">
<input type="checkbox" name="remember" style="align-content: center">记住我
</div>
<input type="submit" class="ui blue submit button"/>
</form>
-
这里登录并不能登录成功,因为接收找不到/login请求,会报404错误 -
必须要将跳转页面加上一个登陆页面里的对应请求 http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");
-
但是它是怎么和我们自己写的页面的username和password对应的呢?看源码 usernameParameter("username")
passwordParameter("password")
-
看来是formLogin这个方法自动帮我们识别了啊,但是如果我们写的网站name不是标准的username和password呢?假如是name和pwd这两个属性,进行如下修改即可 http.formLogin().loginPage("/toLogin").usernameParameter("name").passwordParameter("pwd").loginProcessingUrl("/login");
-
记住我的绑定也和用户名密码差不多,但是它没有默认值,必须要对应
http.rememberMe().rememberMeParameter("remember");
SpringSecurity总结
Shiro
Shiro概述
shiro是Apache的一个java安全权限框架,它可以非常容易开发出足够好的应用,不仅可以用在javaSE环境,也可以应用在javaEE环境(包含自己的session)
shiro可以完成认证、授权、加密、会话管理、web集成、缓存等操作
Shiro十分钟快速开发
快速开发可以参考git项目shiro-root-1.7.1/samples/quickstart/ 该目录下
-
创建一个新的maven项目,删掉src,创建一个新的模块,导入相关依赖 <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
-
配置log4j,创建log4j.properties log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
-
配置shiro,创建shiro.ini [users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
-
导入shiro的java文件 import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
catch (AuthenticationException ae) {
}
}
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
currentUser.logout();
System.exit(0);
}
}
可以看出写这段代码的人是个星战爱好者
- 账号: 孤独的斯塔尔(Lone Starr) 进行登陆操作!
- 如果拥有角色:Schwartz(应该原力)
- 登陆成功提示:愿原力与你同在!
- 登陆失败提示:你好,凡人(无原力者)
- 如果有此权限,提示:你可以使用光剑,用它秀吧!
- 如果没有此权限:抱歉,光剑是绝地武士专属武器
- 最后一个例子: winnebago (lone starr 的飞船名字) eagle5=也许是船长lone starr的驾驶员代号?
- 如果有此权限:你可以使用老鹰5号作为驾驶通行证,给你钥匙!
-
运行,得到如下信息即可
2021-08-24 16:04:20,494 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler… 2021-08-24 16:04:21,728 INFO [Quickstart] - Retrieved the correct value! [aValue] 2021-08-24 16:04:21,729 INFO [Quickstart] - User [lonestarr] logged in successfully. 2021-08-24 16:04:21,729 INFO [Quickstart] - May the Schwartz be with you! 2021-08-24 16:04:21,730 INFO [Quickstart] - You may use a lightsaber ring. Use it wisely. 2021-08-24 16:04:21,730 INFO [Quickstart] - You are permitted to ‘drive’ the winnebago with license plate (id) ‘eagle5’. Here are the keys - have fun!
进程已结束,退出代码为 0
Shiro快速开发的原理
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Subject存在session的值为[" + value + "]");
}
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("该用户名不正确 " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("用户" + token.getPrincipal() + " 不正确");
} catch (LockedAccountException lae) {
log.info("该账户的用户名" + token.getPrincipal() + "被锁定了" +
"请联系您的管理员解锁");
}
catch (AuthenticationException ae) {
}
}
log.info("用户 [" + currentUser.getPrincipal() + "] 成功登陆了");
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
currentUser.logout();
System.exit(0);
}
}
总结下来就是以下代码
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
if (!currentUser.isAuthenticated())
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
currentUser.login(token);
log.info("用户 [" + currentUser.getPrincipal() + "] 成功登陆了");
是否有这个role
if (currentUser.hasRole("schwartz")) {
if (currentUser.isPermitted("lightsaber:wield"))
if (currentUser.isPermitted("winnebago:drive:eagle5"))
currentUser.logout();
整体下来,发现其实和springSecurity很像
Shiro整合Springboot
-
创建springboot项目,导入web依赖和thymeleaf框架 -
测试thymeleaf的th:text标签和controller是否能跑成功 -
没问题之后导入shiro相关依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
-
编写UserRealm授权登录类 package com.michilay.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("执行了授权");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
return null;
}
}
继承AuthorizingRealm 实现授权和认证方法方法 -
编写ShiroConfig配置类 package com.michilay.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 {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
- 代码从下往上编写,先创建realm对象,注册bean,先把自定义的类拿过来交给spring托管
- 中间商DefaultWebSecurityManager管理realm对象,并且也交给spring托管
- ShiroFilterFactoryBean工厂设置安全管理器,注册bean
-
写主页进行跳转 <!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>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>
-
controller package com.michilay.controller;
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/add")
public String toAdd(){
return "user/add";
}
@RequestMapping("user/update")
public String toUpdate(){
return "user/update";
}
}
登录拦截
-
添加过滤器
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
HashMap<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");
bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/toLogin");
return bean;
}
-
写一个登录页面和请求 <!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form th:action="@{/login}">
<p>用户名: <input type="text" name="username"></p>
<p>用户名: <input type="password" name="password"></p>
<input type="submit">
</form>
</body>
</html>
@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);
return "index";
} catch (UnknownAccountException e) {
model.addAttribute("msg","你还未注册,请先注册");
return "login";
}catch (IncorrectCredentialsException e) {
model.addAttribute("msg","密码错误");
return "login";
}h
-
在UserRealm封装用户信息并且进行认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
String name="admin";
String password="123";
UsernamePasswordToken userToken=(UsernamePasswordToken) token;
if (!userToken.getUsername().equals(name)){
return null;
}
return new SimpleAuthenticationInfo("",password,"");
}
Shiro大整合
在之前的项目里增增改改依赖,感觉会增多很多bug出现的频率,这边整合了之前学习的内容,结合如下技术进行一个总结学习
项目搭建
数据库mysql,持久层框架mybatis,数据源druid,前端引擎thymeleaf
-
创建springboot项目,导入web+mysql+mybatis+lombok+thymeleaf依赖,创建后再导入log4j、druid和shiro依赖 完整pom如下 <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
-
application.properties配置 # 应用名称
spring.application.name=springboot-09-shiro-mybatis-druid
# 应用服务 WEB 访问端口
server.port=8080
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mapper/*.xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.michilay.pojo
# THYMELEAF (ThymeleafAutoConfiguration)
# 开启模板缓存(默认值: true )
spring.thymeleaf.cache=true
# 检查模板是否存在,然后再呈现
spring.thymeleaf.check-template=true
# 检查模板位置是否正确(默认值 :true )
spring.thymeleaf.check-template-location=true
#Content-Type 的值(默认值: text/html )
spring.thymeleaf.content-type=text/html
# 开启 MVC Thymeleaf 视图解析(默认值: true )
spring.thymeleaf.enabled=true
# 模板编码
spring.thymeleaf.encoding=UTF-8
# 要被排除在解析之外的视图名称列表,?逗号分隔
spring.thymeleaf.excluded-view-names=
# 要运?于模板之上的模板模式。另? StandardTemplate-ModeHandlers( 默认值: HTML5)
spring.thymeleaf.mode=HTML5
# 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
spring.thymeleaf.prefix=classpath:/templates/
# 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
spring.thymeleaf.suffix=.html
-
application.yaml关于数据源的相关配置 spring:
datasource:
username: root
password: 数据库密码
url: jdbc:mysql://localhost:3306/count_db?serverTimezone=UTC&userUnicode=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: 6000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
userGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true.druid.stat.slowSqlMillis=500
-
log4j.properties日志配置 (idea又崩溃了,我心态炸了) 如下。。。 log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
-
德鲁伊配置类DruidConfig package com.michilay.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.context.properties.ConfigurationProperties;
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();
}
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
HashMap<String, String> initParameters = new HashMap<>();
initParameters.put("loginUsername","admin");
initParameters.put("loginPassword","123");
initParameters.put("allow","");
bean.setInitParameters(initParameters);
return bean;
}
}
-
实体类,mapper,service层,service实现代码,对应的mapper.xml一套,这里先写一个方法 User findUserByName(String userName);
<select id="findUserByName" resultType="User" parameterType="String">
select * from count_db.account where userName=#{userName}
</select>
-
测试 package com.michilay;
import com.michilay.pojo.User;
import com.michilay.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Springboot09ShiroMybatisDruidApplicationTests {
UserService userService;
@Autowired
public Springboot09ShiroMybatisDruidApplicationTests(UserService userService) {
this.userService = userService;
}
@Test
void contextLoads() {
User michilay = userService.findUserByName("michilay");
System.out.println(michilay);
}
}
好的没问题进行下一步 -
将shiro模块的shiroConfig,userRealm以及静态资源导入
登陆验证
-
现在要用shiro来管理用户登录 UserRealm package com.michilay.config;
import com.michilay.pojo.User;
import com.michilay.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;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User userByName = userService.findUserByName(userToken.getUsername());
if (userByName==null){
return null;
}
return new SimpleAuthenticationInfo("",userByName.getUserPwd(),"");
}
}
请求授权
-
写一个未授权请求 @RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未经授权无法访问此页面";
}
-
设置权限过滤功能,ShiroConfig如下 package com.michilay.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.HashMap;
import java.util.LinkedHashMap;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
HashMap<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","authc");
bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/toLogin");
bean.setUnauthorizedUrl("/noauth");
return bean;
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
这样登录add页面必须要求user又add字段才可以 -
我们在UserRealm设置权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:add");
return info;
}
这里给所有的用户都授权,也就是登录增加add权限,实现权限管理功能 -
但是这并不是我们想要的,肯定要区分那些用户有那些权限,这时候就要更改数据库表,增加一个权限字段,修改部分权限 -
更改实体类 -
权限访问更改如下 filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
-
授权如下,直接从数据库里拿到权限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
info.addStringPermission(currentUser.getPerms());
return info;
}
权限分级展示
结合thymeleaf进行权限展示
-
导入依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
-
在ShiroConfig里配置注册
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
-
前端页面 <!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>主页</h1>
<p th:text="${msg}"></p>
<div shiro:guest="true">
<a th:href="@{/toLogin}">登录</a>
</div>
<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>
进入首页,显示登录超链接,点击登录,登陆后登录超链接消失,显示对应的权限页面,例如有add权限的显示add页面
|