HttpSessionContextIntegrationFilter位于过滤器的顶端,
是第一个执行的过滤器。
用途1: 在执行其他过滤器前,判断用户的session是否已经存在了一个springSecurity上下文的 SecurityContext
如果存在就将 SecurityContext取出来 放到 SecurityContextHolder中,供 springSecurity其他部分使用。
如果不存在就创建出一个 SecurityContext 出来,放到SecurityContextHolder中,供 springSecurity其他部分使用。
SecurityContextPersistenceFilter中的源码部分
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
// Crucial removal of SecurityContextHolder contents - do this before anything
// else.
SecurityContextHolder.clearContext();
用途2: 在所有过滤器执行完毕后,清空SecurityContextHolder中的内容
因为SecurityContextHolder是基于ThreadLocal的,如果在操作完成后,没有清空ThreadLocal,会受到服务器线程池机制的影响。
ThreadLocal分析源码部分
ThreadLocal 存放值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递,这样处理后能够优雅的解决实际中的并发问题,
1.set()方法
ThreadLocalMap是ThreadLocal中的静态内部类
static class ThreadLocalMap{}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
主要用于:用户发送注销请求时,销毁用户session,情况securityContextHolder,重定向到注销成功页面,
用于处理Form登录的过滤器,与Form登录的所有操作都在此进行。
Authentication 对象是SpringSecurity使用的进行安全访问的控制用户信息的安全对象
它有未认证和已认证两种状态
UserDetails代表用户信息源
CachingUserDetailsService
EhCacheBasedUserCache
public abstract class AbstractAccessDecisionManager
投票器
public interface AccessDecisionVoter<S> {
// ~ Static fields/initializers
// =====================================================================================
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
实现类
public class RoleVoter implements AccessDecisionVoter
三个投票器:
1.AffirmativeBased : 一票通过
2. ConsensusBased : 有一半及以上投票通过
3. UnanimousBased :所有投票器都通过才允许访问
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mall</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>demo</name>
<description>Demo project for Spring Security</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</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-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
package com.mall.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@RequestMapping("/")
public String home(){
return "HELLO SPRINGBOOT";
}
@RequestMapping("/hello")
public String hello(){
return "hello";
}
}
package com.mall.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 有关HTTP请求哪些请求会被拦截,以及请求该怎么处理
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 项目主路径可放行
.antMatchers("/").permitAll()
// 其他请求要全部经过验证处理
.anyRequest().authenticated()
// 注销允许访问
.and()
.logout().permitAll()
// 支持表单登录
.and()
.formLogin();
//关闭csrf认证
http.csrf().disable();
}
/**
* 忽略静态资源的拦截
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**","/css/**","/images/**");
}
}
/**
* SpringSecurity基于内存的验证方法
* 这是因为Spring boot 2.0.3引用的security 依赖是 spring security 5.X版本,此版本需要提供一个PasswordEncorder的实例,否则后台汇报错误:
* java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
* 并且页面毫无响应。
*
* 因此,需要创建PasswordEncorder的实现类。
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
// 这样,页面提交时候,密码以明文的方式进行匹配。
// todo : 需要添加该passwordEncoder()
.passwordEncoder(new MyPasswordEncoder())
// 指定用户
.withUser("admin")
// 指定用户密码
.password("123456")
// 指定角色
.roles("ADMIN");
}
// 2.1 添加一个访问路径方法
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping("/roleAuth")
public String role(){
return "hello";
}
// 2.2 在主启动类上面添加这个注解,作用是开启 @PreAuthorize效果
@EnableGlobalMethodSecurity(prePostEnabled = true)
// 2.3 在SpringSecurityConfig.class 类方法中增加一个新的用户
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
// 这样,页面提交时候,密码以明文的方式进行匹配。
// todo : 需要添加该passwordEncoder()
.passwordEncoder(new MyPasswordEncoder())
// 指定用户
.withUser("admin")
// 指定用户密码
.password("123456")
// 指定角色
.roles("ADMIN");
// 新增用户
auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder())
.withUser("demo")
.password("demo")
.roles("USER");
}
package com.mall.demo2.model;
import com.mall.demo2.service.UserService;
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.springframework.beans.factory.annotation.Autowired;
import java.util.*;
public class AuthRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 作为授权来用
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 从session中获取用户信息
User user = (User) principalCollection.fromRealm(this.getClass().getName()).iterator().next();
// 创建空的权限集合信息
Set<String> permissionNameList = new HashSet<>();
// 获取当前所有的角色信息
Set<Role> roles = user.getRoles();
// 遍历,如果角色不为空
if (Objects.nonNull(roles)) {
for (Role role : roles) {
Set<Permission> permissions = role.getPermissions();
// 判断permissions是否为空
if (Objects.nonNull(permissions)) {
for (Permission permission : permissions) {
// 判断一个用户是否有权限
permissionNameList.add(permission.getName());
}
}
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permissionNameList);
return info;
}
/**
* 认证登录使用
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
User user = userService.findUserByUsername(username);
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
}
}
创建密码校验规则
package com.mall.demo2;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
/**
* 验证密码是否匹配
*/
public class CredentialMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken passwordToken = (UsernamePasswordToken) token;
// 取出UsernamePasswordToken中的密码
String password = new String(passwordToken.getPassword());
// 取出数据库中的密码
String dbPassword = (String) info.getCredentials();
// 判断是否相同
return this.equals(password,dbPassword);
}
}
定义shiro的配置类(核心配置)
package com.mall.demo2;
import com.mall.demo2.model.AuthRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
@Configuration
public class ShiroConfiguration {
/**
* 当项目启动时,shiroFilter首先会被初始化,会分别传入manager来进行构造
* @param securityManager
* @return
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean factoryBean(@Qualifier("manager") SecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
// 设置登录的url
bean.setLoginUrl("/login");
// 登录跳转的url
bean.setSuccessUrl("/index");
// 没权限访问时跳转的url
bean.setUnauthorizedUrl("/unauthorized");
// 定义核心拦截请求
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/index","authc");
filterChainDefinitionMap.put("/login","anon");
// 设置
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 返回信息
return bean;
}
/**
* @param realm
* @return
* @Qualifier("authRealm") 使用Spring上下文中的authRealm
*/
@Bean("manager")
public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm realm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(realm);
return manager;
}
/**
* 自定义authRealm
*
* @param matcher
* @return
*/
@Bean("authRealm")
public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher) {
// 创建AuthRealm实例
AuthRealm authRealm = new AuthRealm();
authRealm.setCredentialsMatcher(matcher);
return authRealm;
}
;
@Bean("credentialMatcher")
public CredentialMatcher credentialMatcher() {
return new CredentialMatcher();
}
// 配置shiro和Spring关联的几个类
/**
*Spring 对 shiro进行处理时,使用的securityManager就是自定义的
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor attributeSourceAdvisor(@Qualifier("manager") SecurityManager securityManager){
// 创建该实例信息
AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
// 设置
attributeSourceAdvisor.setSecurityManager(securityManager);
return attributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
}
package com.mall.demo2.controller;
import com.mall.demo2.model.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 org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpSession;
@Controller
public class TestController {
/**
* 登录接口
*
* @return
*/
@RequestMapping("/login")
public String login() {
return "login";
}
/**
* 返回登录页面
*
* @return
*/
@RequestMapping("/index")
public String index() {
return "index";
}
/**
* 登录处理接口
*
* @return
*/
@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username") String username,
@RequestParam("password") String password,
HttpSession session) {
// 组成UsernamePasswordToken实例
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
// shiro 认证逻辑
Subject subject = SecurityUtils.getSubject();
try {
// 调用authRealm进行认证
subject.login(token);
// 获取登录用户主体
User user = (User)subject.getPrincipal();
// 将用户存入session
session.setAttribute("user",user);
// 成功返回Index页面
return "index";
}catch (Exception e){
return "login";
}
}
}
在application.properties中加入MVC拦截器
## jsp ##
spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.jsp
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" >
<mapper namespace="com.mall.demo2.mapper.UserMapper">
<resultMap id="userMap" type="com.mall.demo2.model.User">
<id property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<collection property="roles" ofType="com.mall.demo2.model.Role">
<id property="rid" column="rid"/>
<result property="name" column="rname"/>
<collection property="permissions" ofType="com.mall.demo2.model.Permission">
<id property="pid" column="pid"/>
<result property="name" column="name"/>
<result property="url" column="url"/>
</collection>
</collection>
</resultMap>
<select id="findUserByUsername" resultMap="userMap" parameterType="java.lang.String">
select u.*,r.*,p.*
from `user` u
inner join user_role ur on ur.uid = u.uid
inner join role r on r.rid = ur.rid
inner join permission_role rp on rp.rid = r.rid
inner join permission p on p.pid = rp.pid
where u.username = #{username}
</select>
</mapper>
在TestController中添加方法,用于实现case1 ,某个用户拥有访问权限,去访问其他资源
/**
* 退出登录
*
* @return
*/
@RequestMapping("/logout")
public String logout() {
// 取出验证主体
Subject subject = SecurityUtils.getSubject();
if (Objects.nonNull(subject)){
subject.logout();
}
return "login";
}
@RequestMapping("/admin")
@ResponseBody
public String admin(){
return "admin success";
}
在ShiroConfig 方法中添加信息
/**
* 当项目启动时,shiroFilter首先会被初始化,会分别传入manager来进行构造
* @param securityManager
* @return
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean factoryBean(@Qualifier("manager") SecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
// 设置登录的url
bean.setLoginUrl("/login");
// 登录跳转的url
bean.setSuccessUrl("/index");
// 没权限访问时跳转的url
bean.setUnauthorizedUrl("/unauthorized");
// 定义核心拦截请求
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/index","authc");
filterChainDefinitionMap.put("/login","anon");
// 此接口不需要做身份认证
filterChainDefinitionMap.put("/loginUser","anon");
// 拦截其他接口
filterChainDefinitionMap.put("/**","user");
// 设置
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 返回信息
return bean;
}
// 定义不同角色可以访问不同的接口
在ShiroConfiguration类中添加
/**
* 当项目启动时,shiroFilter首先会被初始化,会分别传入manager来进行构造
* @param securityManager
* @return
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean factoryBean(@Qualifier("manager") SecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
// 设置登录的url
bean.setLoginUrl("/login");
// 登录跳转的url
bean.setSuccessUrl("/index");
// 没权限访问时跳转的url
bean.setUnauthorizedUrl("/unauthorized");
// 定义核心拦截请求
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/index","authc");
filterChainDefinitionMap.put("/login","anon");
// 此接口不需要做身份认证
filterChainDefinitionMap.put("/loginUser","anon");
// 定义不同角色可以访问不同的接口
filterChainDefinitionMap.put("/admin","roles[admin]");
// 拦截其他接口
filterChainDefinitionMap.put("/**","user");
// 设置
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 返回信息
return bean;
}
在AuthRealm类中添加 角色名称集合
/**
* 作为授权来用
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 从session中获取用户信息
User user = (User) principalCollection.fromRealm(this.getClass().getName()).iterator().next();
// 创建空的权限集合信息
Set<String> permissionNameList = new HashSet<>();
// 创建角色名称集合
Set<String> roleNameList = new HashSet<>();
// 获取当前所有的角色信息
Set<Role> roles = user.getRoles();
// 遍历,如果角色不为空
if (Objects.nonNull(roles)) {
for (Role role : roles) {
roleNameList.add(role.getRname());
Set<Permission> permissions = role.getPermissions();
// 判断permissions是否为空
if (Objects.nonNull(permissions)) {
for (Permission permission : permissions) {
// 判断一个用户是否有权限
permissionNameList.add(permission.getName());
}
}
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permissionNameList);
// 设置角色名称
info.setRoles(roleNameList);
return info;
}
在ShiroConfiguration 中添加
/**
* 自定义authRealm
*
* @param matcher
* @return
*/
@Bean("authRealm")
public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher) {
// 创建AuthRealm实例
AuthRealm authRealm = new AuthRealm();
// 添加缓存到catch中
authRealm.setCacheManager(new MemoryConstrainedCacheManager());
authRealm.setCredentialsMatcher(matcher);
return authRealm;
}