SpringSecurity认证授权和整合项目



一、快速入门


概述


Spring Security 是 Spring 家族中的一个安全管理框架,应用程序的两个主要区域是“认证”和“授权”(或者访问控制)


认证:


系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录其实就是在进行认证,认证的目的是让系统知道你是谁。


授权:


用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能。


权限数据模型

前面已经分析了认证和授权的概念,要实现最终的权限控制,需要有一套表结构支撑:

用户表t_user、权限表t_permission、角色表t_role、菜单表t_menu、用户角色关系表t_user_role、角色权限关系表t_role_permission、角色菜单关系表t_role_menu。


在这里插入图片描述


上述的7张表就构成了RBAC权限模型:


在这里插入图片描述


  1. 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--SpringSecurity起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 编写controller
package com.execise.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller01 {
    
    

    @RequestMapping("/show")
    public String show(){
    
    
        System.out.println("执行了show方法!~~!");
        return "show ... success...";
    }
}
  1. 访问controller
  1. 会出现登录页面,表明springsecurity已经开始工作

在这里插入图片描述


  1. 输入默认的用户名: user , 密码通过控制台可以找到springsecurity产生的密码

在这里插入图片描述


  1. 细节处理:

自定义用户名和密码


application.yml里面可以配置自定义的用户名和密码


在这里插入图片描述


修改日志级别


默认打印的springsecurity的日志级别是 info级别,如果想观察到更详细的日志信息,可以在application.yml 里面修改日志的打印级别 。 如果info看不到日志,可以尝试再设置低一些级别:

比如: debug 或者 trace 级别


log.info()、log.error()


日志级别:fatal>error>warn>info>debug>trace

logging:
  level:
    org:
      springframework:
        security: info

  1. 自动配置原理

springboot自动配置

默认情况下,只要在springboot项目里面添加了springsecurity依赖,那么springsecurity即会自动工作。这个自动生效的配置是由springboot来完成的。

  1. 在启动类身上的注解@SpringBootApplication 即是一切自动配置的开始

  2. 进入该注解内部有一个注解 : @EnableAutoConfiguration , 表示启动自动配置

  3. @EnableAutoConfiguration 注解里面,导入了 AutoConfigurationImportSelector 自动配置导入选择器

  4. 在这个自动导入选择器类AutoConfigurationImportSelector中的 getCandidateConfigurations 可以看到自动导入的类位于 META-INF/spring.factories 文件中。

  5. 该文件位于 springboot的autoconfigure包中


在这里插入图片描述


  1. 在spring.factories文件中搜索security关键字,找到执行 springsecurity 自动配置的类:SecurityAutoConfiguration

  2. 在SecurityAutoConfiguration里面发现它在上面使用 @Import导入 SpringBootWebSecurityConfiguration 类。

  3. SpringBootWebSecurityConfiguration 会对所有的请求进行拦截,自此springsecurity的自动配置解析完毕:


在这里插入图片描述


springsecurity原理分析

springsecurity的核心即是: 过滤器Filter , 翻开springsecurity的官方文档,找到如下说明:


在这里插入图片描述


  1. 这个对象的产生是位于:WebSecurityConfiguration中的springSecurityFilterChain方法中

  2. 通过debug发现,总共有15个过滤器需要配置,这些过滤器各司其职,每个过滤器负责的功能都不一样!


在这里插入图片描述
在这里插入图片描述


  1. 参照官方文档的说明,可以看到有对springsecurity过滤器的描述、以及流程解释:

在这里插入图片描述

在这里插入图片描述


二 、认证授权


在DefaultWebSecurityCondition 里面存在注解

@ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class,… }) 表明如果在JVM中缺失

WebSecurityConfigurerAdapter 则会启用默认的springsecurity的认证和授权流程。 所以如果我们希望自己执行认

证和授权,那么则需要编写一个类,继承 WebSecurityConfigurerAdapter 即可。


  1. 内存方式

一般在开发当中,我们都会选择自己来认证授权!


定义配置类

//为了让spring发现我们写的配置类,需要加上 @Configuration
@Configuration
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
    
    
    
}

重写方法


认证方法

   /*
        认证:
            什么样的账号和密码, 是什么样的角色
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        //内存中的认证:
        auth.inMemoryAuthentication()
                .withUser("zhangsan")
                .password("{noop}123456")
                .roles("ZS")
                .and()
                .withUser("admin")
                .password("{noop}666")
                .roles("ADMIN");
    }

授权方法

/**
     * 授权:
     *  1. 什么样的请求地址允许直接放行,
     *  2. 什么样的请求需要有角色权限,'
     *  3. 其他的请求全部要求认证通过之后才能访问。
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                 // 直接访问,不需要登录
                .antMatchers("/login.html" , "/**/*.css" 
                             , "/**/*.html", "/**/*.js").permitAll()
                 //需要角色是AD
                .antMatchers("/show","/show01","/show02").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and() //and 用来拼接配置
                .formLogin(); // 表示使用默认springsecurity的登录页面。
    }

异常处理:

当我们已经登录,但是访问并不具有访问权限的资源时,那么会出现403 的异常:


在这里插入图片描述


解决办法: 在授权方法里面添加关于异常的处理办法:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                 // 直接访问,不需要登录
                .antMatchers("/login.html" , "/**/*.css" , "/*/*.html", "/**/*.js").permitAll()
                 //需要角色是AD
                .antMatchers("/show","/show01","/show02").hasRole("admin")
                .anyRequest().authenticated()
                .and() //and 用来拼接配置
                .formLogin(); // 表示使用默认springsecurity的登录页面。

        //异常处理
        http.exceptionHandling()
                .accessDeniedHandler(new AccessDeniedExceptionHandler());
    }


异常处理类


public class AccessDeniedExceptionHandler implements AccessDeniedHandler {
    
    

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    
    
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("权限不足,禁止访问!");
    }
}


自定义登录页面

默认springsecurity的登录操作,会被过滤器: UsernamePasswordAuthenticationFilter 拦截,它内部有几个判定: 请求方式是 post, 请求地址是 /login , 用户名参数是: username , 密码参数名是: password

请求方式不能修改之外,其他的都可以修改


定义登录页面

<form action="/login" method="post">
    用户名: <input type="text" name="username"/><br/>
    密  码: <input type="text" name="password"/><br/>
    <input type="submit" value="登录"/>
</form>

配置

/**
     * 授权:
     *  1. 什么样的请求地址允许直接放行,
     *  2. 什么样的请求需要有角色权限,'
     *  3. 其他的请求全部要求认证通过之后才能访问。
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.formLogin()
                .loginPage("/login.html") //登录页面
                .usernameParameter("username") //用户名参数名称
                .passwordParameter("password") //密码参数名称
                .loginProcessingUrl("/login") //登录的请求地址
                .defaultSuccessUrl("/index.html",true) //登录成功之后的页面地址
                .failureForwardUrl("/login.html") //登录失败之后的页面地址
                .and().csrf().disable();
    }

csrf


CSRF(Cross-site request forgery),中文名称:跨站请求伪造,缩写为:CSRF/XSRF。

一般来说,攻击者通过伪造用户的浏览器的请求,向访问一个用户自己曾经认证访问过的网站发送出去,使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假消息等。


在这里插入图片描述


在这里插入图片描述


参考网址:https://blog.csdn.net/qq_45803593/article/details/124727762


退出登录

    /**
     * 授权:
     *  1. 什么样的请求地址允许直接放行,
     *  2. 什么样的请求需要有角色权限,'
     *  3. 其他的请求全部要求认证通过之后才能访问。
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.formLogin()
                .loginPage("/login.html") //登录页面
                .usernameParameter("username") //用户名参数名称
                .passwordParameter("password") //密码参数名称
                .loginProcessingUrl("/login") //登录的请求地址
                .defaultSuccessUrl("/index.html") //登录成功之后的页面地址
                .failureForwardUrl("/login.html") //登录失败之后的页面地址\
                .and().csrf().disable() //禁用csrf
                .logout() //退出登录配置
                .logoutUrl("/logout") //退出请求
                .logoutSuccessUrl("/login.html"); //退出成功到达页面
    }

密码加密


在springsecurity里面,支持的加密方式有很多, 具体可以在PasswordEncoderFactories 里面查看。官方推荐使用 bcrypt 加密方式。


在这里插入图片描述


MD5:加密方式 不可逆 但是每次加密的结果都是一样的

针对它的缺陷,解决方式:

  • 方式一:加密多次 至少3次以上

  • 方式二:Md5(password+salt) salt:随机字符串 UUID

bcrypt:将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题

spring security中的BCryptPasswordEncoder方法采用SHA-256 +随机盐+密钥 对密码进行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(这个与编码/解码一样),但是采用Hash处理,其过程是不可逆的。

(1)加密(encode):注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。

(2)密码匹配(matches):用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。如果两者相同,说明用户输入的密码正确。

这正是为什么处理密码时要用hash算法,而不用加密算法。因为这样处理即使数据库泄漏,黑客也很难破解密码。


加密结果解释:

$2a$10$/bTVvqqlH9UiE0ZJZ7N2Me3RIgUCdgMheyTgV0B4cMCSokPa.6oCa

加密后字符串的长度为固定的60位。其中:

$是分割符,无意义;

2a是bcrypt加密版本号;

10是循环10次加盐加密;

而后的前22位是salt值;

再然后的字符串就是密码的密文了。


具体应用

要想使用bcrypt密码加密其实很简单,只需要在配置类中定义一个方法,返回BcryptPasswordEncoder 对象即可,并且不要忘记了对认证的用户密码进行加密处理。

用户在输入密码登录时,SpringSecurity就会自动对用户输入的密码使用bcrypt进行加密 然后和内存|数据库中存储的用户密码进行比对 注意:内存|数据库中存储的用户密码也要使用bcrypt进行加密

密码加密使用步骤:

  1. 对用户输入的密码进行加密

  2. 对内存|数据库中存储的密码进行加密

注意:两边的加密方式要一致


添加方法

@Bean
public BCryptPasswordEncoder bp (){
    
    
   return new BCryptPasswordEncoder();
}

认证处理

    /*
        认证:
            什么样的账号和密码, 是什么样的角色

     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        //内存中的认证:
        auth.inMemoryAuthentication()
                .withUser("zhangsan")
                //.password("{noop}123456") // 明文密码
             	// bcrpt加密处理 , 使用这种方式,就不需要在上面的bp() 上面打上注解 @Bean了
                //.password("{bcrypt}"+bp().encode("123456"))
                .password(bp().encode("123456")) // bcrpt加密处理
                .roles("USER")
                .and()
                .withUser("admin")
                .password(bp().encode("666"))
                .roles("ADMIN");
    }

  1. 数据库方式

# 创建用户表
create table t_user(
    id bigint primary key auto_increment ,
    username varchar(25) ,
    password varchar(65) ,
    name varchar(25));

# 创建角色表
create table t_role(
    id int primary key auto_increment,
    name varchar(25) ,
    keyword varchar(25)
);

# 创建用户角色表
create table t_user_role(uid bigint , rid int );

# 添加外键
alter table t_user_role add constraint FK_user_ur foreign key (uid) references t_user(id);
alter table t_user_role add constraint FK_role_ur foreign key (rid) references t_role(id);

# 添加数据 密码是 123456 加密后的效果
insert into t_user values ( null , 'zhangsan' , '$2a$10$ezVuPO6NaUsQZ66p7y0QSeWfO6s.Qoz01vbTcI2vlcuLXy8Wk.DOy' , '张三');
insert into t_user values ( null , 'lisi' , '$2a$10$ezVuPO6NaUsQZ66p7y0QSeWfO6s.Qoz01vbTcI2vlcuLXy8Wk.DOy' , '李四');

insert into t_role values ( null , '管理员' , 'ROLE_ADMIN' );
insert into t_role values ( null , '普通员工' , 'ROLE_USER' );

insert into t_user_role value( 1 , 1 );
insert into t_user_role value( 2 , 2 );

创建项目,添加mybatisplus、mysql驱动、lombok依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

application.yml中配置数据源

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/day44?useSSL=false&serverTimezone=UTC
    username: root
    password: root

准备实体类


User

package com.execise.bean;

import lombok.Data;

import java.util.List;

@Data
public class User {
    
    

    private long id ;
    private String username;
    private String password;
    private String name;

    //用于记录用户的角色信息,一个用户可以有多个角色身份。
    private List<Role> roleList;
}


Role

package com.execise.bean;

import lombok.Data;

@Data
public class Role {
    
    
    private long id ;
    private String name;
    private String keyword;
}


准备dao


UserDao

package com.execise.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.execise.bean.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserDao  extends BaseMapper<User> {
    
    
    /**
     * 根据用户名查询用户
     * @param username
     * @return
     */
    User findByUsernameUser(String username);
}


UserDao.xml

位于: static/mapper/UserDao.xml


<resultMap id="userMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username" />
    <result property="password" column="password" />
    <result property="name" column="name" />
    <!--collection:配置一对多 将查询出来的每条角色信息封装到一个Role对象中 最终存入到roleList集合中-->
    <collection property="roleList" ofType="Role" columnPrefix="r_">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="keyword" column="keyword"/>
    </collection>
</resultMap>

<select id="findByUsernameUser" resultMap="userMap">
    select u.*,r.id r_id,r.name r_name,r.keyword r_keyword from t_user AS u
        inner join t_user_role AS ur on u.id=ur.uid
        inner join t_role AS r on ur.rid = r.id
        where username=#{username}
</select>

application.yml


mybatis-plus:
  type-aliases-package: com.execise.bean
  mapper-locations: classpath:mapper/*.xml

准备配置类

package com.execise.service.impl;

import com.execise.bean.Role;
import com.execise.bean.User;
import com.execise.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserDetailServiceImpl implements UserDetailsService {
    
    

    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    

        //1. 根据用户名查询用户
        User user = userDao.findByUsernameUser(username);
        System.out.println("user = " + user);

        //2. 构建返回
        List<GrantedAuthority> list = new ArrayList<>();
        List<Role> roleList = user.getRoleList();
        for (Role role : roleList) {
    
    
            list.add(new SimpleGrantedAuthority(role.getKeyword()));
        }

        //返回用户详情信息
        return new org.springframework.security.core.userdetails.User(
                user.getUsername() ,
                user.getPassword() ,
                list);
    }
}


修改认证方法

@Configuration
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
    
    

    @Autowired
    private UserDetailServiceImpl us;


    @Bean
    public BCryptPasswordEncoder bp (){
    
    
        return new BCryptPasswordEncoder();
    }

    /*
        认证:
            什么样的账号和密码, 是什么样的角色

     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(us);
    }

    //...授权方法 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    

        http.authorizeRequests()
                .antMatchers("/**/*.html","/**/*.css","/**/*.js").permitAll()
                .antMatchers("/show").hasRole("ADMIN")
                .antMatchers("/show02").hasRole("USER")
                .anyRequest().authenticated()
                .and().formLogin()
                .loginPage("/login.html")
             .loginProcessingUrl("/login").defaultSuccessUrl("/index.html",true).failureForwardUrl("/login.html")
                .and().csrf().disable()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/login.html")
                //403异常处理
                .and().exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
    
    
                    @Override
                    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    
    
                        response.setContentType("text/html;charset=UTF-8");
                        response.getWriter().print("权限不足 禁止访问");
                    }
                });
    }

}

注解动态授权

springsecurity允许程序员在Controller方法上使用注解来动态授权,即配置角色|权限到方法上。表明需要具备什么样的角色身份或者是权限,才允许访问该方法!


  1. 在方法上使用 @PreAuthorize 进行调用 权限拦截
    //表明调用方法需具有该角色身份。
	@PreAuthorize("hasRole('ADMIN')")
    @RequestMapping("/show66")
    public String show66(){
    
    
        System.out.println("执行了show66方法!~~!");
        return "show ... success...";
    }
  1. 要想让注解生效,需要在启动类上面设置注解
//启用全局方法注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class Demo1HelloworldApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(Demo1HelloworldApplication.class, args);
    }

}

三 、整合项目


  1. 添加依赖

    <!--添加springsecurity-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

  1. 创建表

角色表

create table role
(
    id      bigint auto_increment primary key,
    name    varchar(100) null,
    keyword varchar(25)  null
);

员工角色表

create table if not exists employee_role
(
	eid bigint null,
	rid bigint null,
	constraint user_role_role_id_fk
		foreign key (rid) references role (id),
	constraint user_role_user_id_fk
		foreign key (eid) references employee (id)
);


  1. 编写实体类

Role

package com.execise.bean;

import lombok.Data;

@Data
public class Role {
    
    
    private long id ;
    private String name;
    private String keyword;
}


Employee

让Employee实现UserDetails 这样在认证方法返回即可只返回Employee对象即可

因为它里面包含了权限内容


@Data
public class Employee implements Serializable , UserDetails {
    
    

    private static final long serialVersionUID = 1L;


    @TableField(exist = false)
    private List<Role> roleList = new ArrayList<>();

    //原有属性省略...

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
    
        List<GrantedAuthority> list = new ArrayList<>();
        roleList.forEach(r->{
    
    
            list.add(new SimpleGrantedAuthority(r.getKeyword()));
        });
        return list;
    }

    @Override
    public boolean isAccountNonExpired() {
    
    
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
    
    
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
    
    
        return true;
    }

    @Override
    public boolean isEnabled() {
    
    
        return true;
    }
}

  1. 编写Dao
public interface EmployeeDao extends BaseMapper<Employee> {
    
    
    /**
     * 根据用户名来查询员工信息及角色身份列表信息
     * @param username
     * @return
     */
    Employee findByUsername(String username);
}

  1. 编写映射文件

EmployeeMapper.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.execise.dao.EmployeeDao">

    <resultMap id="empMap" type="Employee">
       <id property="id" column="id"/>
       <result property="name" column="name"/>
       <result property="username" column="username"/>
       <result property="password" column="password"/>
       <collection property="roleList" ofType="Role" columnPrefix="r_">
           <result property="id" column="id"/>
           <result property="name" column="name"/>
           <result property="keyword" column="keyword"/>
       </collection>
   </resultMap>

   <select id="selectByUsername" resultMap="empMap">
       select e.*,r.id r_id,r.name r_name,r.keyword r_keyword from employee as e
           inner join employee_role er on e.id = er.eid
           inner join role r on er.rid = r.id
           where e.username=#{username}
   </select>
</mapper>

application.yml

---
mybatis-plus:
  mapper-locations: classpath:/mapper/*.xml
  type-aliases-package: com.execise.bean

  1. 编写认证处理类
package com.execise.security;

import com.execise.bean.Employee;
import com.execise.bean.Role;
import com.execise.dao.EmployeeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class EmployeeDetailsServiceImpl  implements UserDetailsService {
    
    

    @Autowired
    private EmployeeDao dao;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
    
        //根据用户名来查询用户信息
        return dao.findByUsername(s);
    }
}


7 . 编写过滤器类

package com.execise.security;

import com.alibaba.fastjson.JSON;
import com.execise.bean.Employee;
import com.execise.config.BaseContext;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 自己定义的过滤器,用来顶替springsecurity的账号密码校验的过滤器
 */
public class LoginFilter02 extends UsernamePasswordAuthenticationFilter {
    
    

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    
    
        try {
    
    
            //如果不是post请求,那么直接抛出异常,因为一般登录操作,都是走post请求。
            if (!request.getMethod().equals("POST")) {
    
    
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            } else {
    
    
				//获取登录 ajax请求携带的json格式用户名和密码封装到Employee对象中
                Employee e = JSON.parseObject(request.getInputStream() , Employee.class ) ;
                System.out.println("LoginFilter02:: 页面传递上来的对象:" + e);
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(e.getUsername(), e.getPassword());
                this.setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return super.attemptAuthentication(request,response);
    }
}



  1. 编写认证授权配置类
package com.execise.security;

import com.alibaba.fastjson.JSON;
import com.execise.bean.Employee;
import com.execise.common.R;
import com.execise.config.BaseContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Configuration
public class SpringSecurityConfig  extends WebSecurityConfigurerAdapter {
    
    

    @Autowired
    private BaseContext baseContext;

    @Autowired
    private EmployeeDetailsServiceImpl es;

    @Bean
    public BCryptPasswordEncoder bcr(){
    
    
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(es);
    }


    @Bean
    public LoginFilter02 loginFilter02 () throws Exception {
    
    
        //1.构建登录过滤器对象
        LoginFilter02 filter = new LoginFilter02();
        //2.设置登录过滤器拦截地址
        filter.setFilterProcessesUrl("/employee/login");
        //3.设置认证管理员 将自定义的登录过滤器管理器加入到SpringSecurity认证管理员管理
        filter.setAuthenticationManager(authenticationManagerBean());
        
        //4.设置登录成功处理
        filter.setAuthenticationSuccessHandler((req,resp,auth)->{
    
    
            String json = JSON.toJSONString(R.success("登录成功"));
            resp.setContentType("application/json;charset=utf-8");
            resp.getWriter().write(json);
        });
        
        //5.设置登录失败处理
        filter.setAuthenticationFailureHandler((req, resp ,ex)->{
    
    
            resp.setContentType("application/json;charset=utf-8");
            String json = JSON.toJSONString(R.error("登录失败"));
            resp.getWriter().write(json);
        });
        return filter;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
    
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.formLogin()
                 .loginPage("/backend/page/login/login.html").permitAll()
                .and().logout().logoutUrl("/logout")
                .logoutSuccessHandler((req, resp , auth) ->{
    
    
                    String json = JSON.toJSONString(R.success("退出登录成功"));
                    resp.getWriter().write(json);
                })
                .and().authorizeRequests()
                .antMatchers("/**/*.js" , "/**/*.css" , "/**/styles/**","/sms/code" , "/**/*.ico" , "/**/images/**").permitAll()
                .anyRequest().authenticated()
                .and().headers().frameOptions().sameOrigin() //加入同源策略,这样即可允许页面上加载iframe子页面
                .and().csrf().disable();


        http.addFilterAt(loginFilter02() , UsernamePasswordAuthenticationFilter.class);
    }
}

  1. 修改公共字段填充
package com.execise.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.execise.bean.Employee;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;

//把这个类交给spring管理
@Component
public class CommonFieldHandler implements MetaObjectHandler {
    
    

    @Autowired
    private BaseContext baseContext;

    @Override
    public void insertFill(MetaObject metaObject) {
    
    
        System.out.println("添加操作:::  来调用insertFill给公共字段赋值了!~~!");
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());

        //从springsecurity作用域对象中获取当前管理的认证用户对象【相当于session中存储】
        Employee e = (Employee) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        metaObject.setValue("createUser" , e.getId());
        metaObject.setValue("updateUser" , e.getId());
    }


    //当mybatisplus 执行更新操作的时候,会通过这个方法给公共字段赋值。
    @Override
    public void updateFill(MetaObject metaObject) {
    
    
        metaObject.setValue("updateTime", LocalDateTime.now());

        //从springsecurity中获取数据
        Employee e = (Employee) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        metaObject.setValue("updateUser" ,e.getId());
    }
}

  1. 使用动态鉴权

在category的添加分类方法上,使用注解标记需要什么样的权限才能访问该方法。

    /**
     * 添加分类
     * @param category
     * @return
     */
    @PreAuthorize("hasRole('ADMIN')")
    @PostMapping
    public R<String> add(@RequestBody Category category){
    
    

        //1. 调用service
        int row = cs.add(category);

        //2. 返回
        if(row > 0 ){
    
    
            return R.success("添加分类成功!");
        }else{
    
    
            return R.error("添加分类失败!");
        }
    }

启动类添加开启全局注解开关

package com.execise;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@MapperScan("com.execise.dao") //扫描dao接口。
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ReggieApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(ReggieApplication.class , args);
    }
}

猜你喜欢

转载自blog.csdn.net/m0_67559541/article/details/126978696