spring security和shiro
Spring Security的使用
官网:https://spring.io/projects/spring-security
帮助文档:https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5
1、什么是Spring Security?
Spring Security 是Spring项目的安全框架,它可以实现web的安全机制,侧重于java应用程序提供身份验证(Authentication)和授权(Authorization)。
2、怎样使用Spring Security?
需要引入security启动器
<dependency>
groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3.Spring Security 核心类
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
4、spring security的目标
认证(Authentication)
身份验证是基于用户名/用户id。以此验证你的身份
授权 (Authorization)
授权是基于身份验证之后,最终会授予你的访问资源(信息,文件等)
访问资源权限案例
1.搭建spring boot项目,导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--导入security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.导入静态资源
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1bG3LQlj-1617546022033)(C:\Users\拾忆\AppData\Roaming\Typora\typora-user-images\image-20210403163249467.png)]
3.controller
@Controller
public class RouterController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/tologin")
public String toLogin(){
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;
}
}
#关闭thymeleaf缓存
spring.thymeleaf.cache=false
权限控制和注销
根据用户的角色访问对应的页面,例如:基于root用户可以访问所有页面,普通用户sun,只能访问指定的页面。
4.config
@EnableWebSecurity//导入websecurity依赖
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") ;
//开启默认的登录页面
http.formLogin();
//开启注销,并定位到首页
http.logout().logoutSuccessUrl("/");
}
@Override
//认证 springboot 2.1.x 可以直接使用
//密码编码 passwordEncoder
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//拿取资源方式:内存获取,数据库中获取
//本案例是从内存中拿取资源
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("root").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1","vip2","vip3")
.and()
.withUser("sun").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1")
.and()
.withUser("li").password(new BCryptPasswordEncoder().encode("12345")).roles("vip2");
}
}
注意:
修改前端index页面
1.导入命名空间
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
2.修改导航栏增加判定认证
<!--主容器-->
<div class="ui container">
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">首页</a>
<!--登录注销-->
<div class="right menu">
<!--未登录-->
<div sec:authorize="!isAuthenticated">
<a class="item" th:href="@{/login}">
<i class="address card icon"></i> 登录
</a>
</div>
<!--如果登录,则显示注销 用户名-->
<div sec:authorize="isAuthenticated">
<a class="item">
用户名:<span sec:authentication="name"></span>
角色:<span sec:authentication="principal.authorities"></span>
</a>
</div>
<div sec:authorize="isAuthenticated">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
<!--已登录
<a th:href="@{/usr/toUserCenter}">
<i class="address card icon"></i> admin
</a>
-->
</div>
</div>
</div>
3.权限认证
sec:authorize="hasRole('vip1')
<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>
记住我
设置之前,完成登录页面,在关闭浏览器,会重新登录。设置之后,完成登录页面,在关闭浏览器,会自动登录。
@Override
protected void configure(HttpSecurity http) throws Exception {
//记住我
http.rememberMe();
}
在登录页添加记住我 多选框
<input type="checkbox" name="remember"> 记住我
后台接收前端参数
http.rememberMe().rememberMeParameter("remember");
结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销时,会删除这个cookie。
定制登录页
http.formLogin().loginPage("/toLogin");
注意:登录表单的请求必须是post
<form th:action="@{/usr/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>
<input type="submit" class="ui blue submit button"/>
</form>
接收登录的用户和密码参数
http.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/toLogin")
.loginProcessingUrl("/login");
结果展示:
认识shiro
github:https://github.com/apache/shiro
1.什么是shiro?
shiro和spring security一样,一个java安全的框架。它可以完成认证、授权、加密、会话、缓存等功能。
2.shiro的核心组件
- Subject 主体,包括Principle和Credentials
- SecurityManager 安全管理员,是shiro的核心
- Realms 域,充当安全数据的“桥梁”。
联系:
Subject是应用代码的直接交互对象,Subject将所有的交互信息委托给SecurityManager ,它负责与shiro的其他组件进行交互。SecurityManager 要验证用户身份,需要从realm中获取相应的用户进行比较,来确定用户身份是否合法,也需要从中得到用户相应的角色和权限,从而验证用户的操作是否能进行,
3.shiro认证(Authentication)
主要用于身份验证,一般通过核对用户姓名和用户id.
3.1.shiro认证中的关键认证对象
- subject 主体(可以是人、物)
- Principle 身份信息 (可以是用户名、邮件)
- credentials 凭证信息(密码、数字证书)
4.shiro认证流程
自定义Realm
认证时需要自己实现realm去获取特定的数据,如验证账号密码等。
public class CustomRealm1 extends AuthorizingRealm {
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//从token中 获取用户身份信息
String username = (String) token.getPrincipal();
//拿username从数据库中查询
//....
//如果查询不到则返回null
if(!username.equals("zhang")){//这里模拟查询不到
return null;
}
//获取从数据库查询出来的用户密码
String password = "123";//这里使用静态数据模拟。。
//返回认证信息由父类AuthenticatingRealm进行认证
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
username, password, getName());
return simpleAuthenticationInfo;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
return null;
}
}
shiro授权认证登录案例:
依赖
<dependencies>
<!-- shiro整合spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<!-- log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!--mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
</dependencies>
pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
private String perms;
}
mapper:userMapper
@Repository
@Mapper
public interface UserMapper {
public User queryUserByName(String name);
}
UserMapper.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">
<!--namespace绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.sun.mapper.UserMapper">
<select id="queryUserByName" resultType="com.sun.pojo.User" parameterType="String">
select * from user where name = #{name};
</select>
</mapper>
controller:MyController
package com.sun.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","shiro springboot");
return "index";
}
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/login")
public String login(String name, String password, Model model){
//获得当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
//执行登录方法,若无异常说明登录成功
try {
subject.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 unauthrized(){
return "未授权不能访问此页面";
}
}
service:UserService、UserServiceImpl
public interface UserService {
public User queryUserByName(String name);
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
config:UserRealm
package com.sun.config;
import com.sun.pojo.User;
import com.sun.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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
//自定义UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserServiceImpl userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了---》授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//拿到当前的登录对象,拿到user
Subject subject = SecurityUtils.getSubject();
User principal = ( User)subject.getPrincipal();
info.addStringPermission(principal.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了---》认证doGetAuthenticationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//连接数据库,获取数据库的username
User user = userService.queryUserByName(userToken.getUsername());
if(user==null){
return null;
}
//获取session
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("loginUser",user);
//密码认证
return new SimpleAuthenticationInfo(user,user.getPassword(),user.getName());
}
}
shiroconfig
package com.sun.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
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 3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
factoryBean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
anon: 无需认证就可以访问
authc:必须认证了才能访问
user: 必须拥有 记住我功能才能访问
perms: 拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/
//拦截
Map<String, String> filterMap = new LinkedHashMap<>();
// filterMap.put("/user/add","authc");
// filterMap.put("/user/update","authc");
filterMap.put("/user/*","authc");
/*授权*/
filterMap.put("/user/update","perms[user:update]");
filterMap.put("/user/add","perms[user:add]");
factoryBean.setFilterChainDefinitionMap(filterMap);
// 设置登录的请求
factoryBean.setLoginUrl("/toLogin");
//未授权请求
factoryBean.setUnauthorizedUrl("/noauth");
factoryBean.setFilterChainDefinitionMap(filterMap);
return factoryBean;
}
//defaultWebSecurityManager 2
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联userRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建realm对象,需要自定义 1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
@Bean
public ShiroDialect getShrioDialect(){
return new ShiroDialect();
}
}
templates:User/add.html ,update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>add</title>
</head>
<body>
<h3>add</h3>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>update</title>
</head>
<body>
<h3>update</h3>
</body>
</html>
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</title>
</head>
<body>
<h4>首页</h4>
<!--若存在session则不显示登录按钮,存在则显示登录按钮-->
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
login.html
<!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}" style="color: red"></p>
<form th:action="@{/login} ">
<P> 用户名:<input type="text" name="username"></P>
<p>密码:<input type="password" name="password"></p>
<p> <input type="submit"></p>
</form>
</body>
</html>
application.properties
mybatis.type-aliases-package=com.sun.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
application.yml
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/userboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
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:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500