栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

通俗易懂的SpringSecurity学习笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

通俗易懂的SpringSecurity学习笔记

侵删!侵删!侵删!

文章目录
  • 基本概念介绍
    • 介绍
    • 认证
    • 授权
  • 项目搭建
  • 内存认证
  • 自定义认证逻辑
  • 密码解析器PasswordEncoder
  • 自定义登录界面
    • 编写登录页面
    • 在Spring Security配置类自定义登录页面
  • 会话管理
    • 认证成功后的处理方式
    • 认证失败后的处理方式
    • 退出登录
    • 退出成功处理器
    • 记住我
  • 授权
    • Role-Based Access Control
    • Resource-Based Access Control
  • 授权__权限表设计
  • 编写查询权限的方法
  • 配置类设置访问控制
  • 自定义访问控制逻辑
  • 注解设置访问控制
  • 在前端进行访问控制
  • 总结

基本概念介绍

源码:https://gitee.com/DoubleW2w/spring-security-learning

介绍

SpringSecurity 安全服务框架,核心功能是认证和授权。提供了生命是安全访问控制功能,减少为了系统安全而编写大量重复代码的工作。

认证

认证即判断用户的身份是否合法,合法则继续访问,不合法则拒绝访问。

  • 用户密码登录,二维码登录,手机短信登录,人脸识别,指纹认证

目的:保护系统隐私数据与资源

授权

认证通过后,根据用户的权限来控制用户访问资源的过程。普通用户只能看普通资源,VIP用户能看VIP资源。

认证是保证用户身份的合法性,授权则是为了更细粒度对数据进行划分,控制不同的用户能够访问不同的资源

项目搭建

依赖pom文件

    
        
            org.springframework.boot
            spring-boot-starter
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
            org.springframework.boot
            spring-boot-starter-security
        
        
            mysql
            mysql-connector-java
            runtime
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.4.3.4
        
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    
package com.doublew2w.controller;

@Controller
public class PageController {

    @RequestMapping("/{page}")
    public String showPage(@PathVariable String page) {
        return page;
    }
}
package com.doublew2w;

@SpringBootApplication
public class Application {

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

}

resources.templates




    
    
    
    登录首页


    

主页面

resources

server:
  port: 80
#日志格式
logging:
  pattern:
    console: '%d{yyyy-MM-dd HH:mm:ss, SSS} %clr(%-5level) --- [%thread]  %cyan(%-50logger{50}):%msg%n'

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mysecurity?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

启动项目,访问项目主页面 http://localhost/main,项目会自动跳转到一个登录页面。这代表Spring Security已经开启了认证
功能,不登录无法访问所有资源,该页面就是 Spring Security 自带的登录页面。

我们使用 user 作为用户名,控制台中的字符串作为密码登录,登录成功后跳转到项目主页面。

内存认证

Spring Security 会将登录页传来的用户名密码和内存中的用户名密码做匹配认证,使用到的类是 InMemoryUserDetailsManager

@Configuration
public class SecurityConfig {
    
    @Bean
    public UserDetailsService userDetailsService() {
        // 1.使用内存数据进行认证
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 2.创建两个用户
        UserDetails user1 = User.withUsername("baizhan").password("123").authorities("admin").build();
        UserDetails user2 = User.withUsername("sxt").password("456").authorities("admin").build();
        // 3.将这两个用户添加到内存中
        manager.createUser(user1);
        manager.createUser(user2);
        return manager;
    }

    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

将登录页传来的用户名密码和内存中用户名密码做匹配认证。

自定义认证逻辑

在实际项目中,认证逻辑是需要自定义控制的。将 UserDetailsService 接口的实现类放入Spring容器即可自定义认证逻辑

InMemoryUserDetailsManager 就是 UserDetailsService 接口的一个实现类,它将登录页传来的用户名密码和内存中用户名密码做匹配认证。当然我们也可以自定义 UserDetailsService 接口的实现类

public interface UserDetailsService {
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

UserDetailsService 的实现类必须重写 loadUserByUsername() 方法,该方法定义了具体的认证逻辑。

参数 username 是前端传来的用户名,我们需要根据传来的用户名查询到该用户(一般是从数据库查询),并将查询到的用户封装成一个 UserDetails 对象,该对象是 Spring Security 提供的用户对象,包含用户名、密码、权限。Spring Security 会根据UserDetails 对象中的密码和客户端提供密码进行比较。相同则认证通过,不相同则认证失败。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据库建表

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROp TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (1, 'baizhan', 'baizhan', '13812345678');
INSERT INTO `users` VALUES (2, 'sxt', 'sxt', '13812345678');
INSERT INTO `users` VALUES (3, 'demo', '$2a$10$tBjD3Sdp6RGZdTlJoHns..3Yodf.DzahrkVHVoQGKSHCTxavCelHO', NULL);

编写实体类

@Data
public class Users {
    private Integer id;
    private String username;
    private String password;
    private String phone;
}

编写dao接口

public interface UsersMapper extends BaseMapper {
}

在 SpringBoot 启动类中添加 @MapperScan 注解,扫描Mapper文件夹

@SpringBootApplication
@MapperScan("com.doublew2w.mapper")
public class Application {
    public static void main(String[] args)
    {
    	SpringApplication.run(MysecurityApplication.class, args);
    }
}

创建 UserDetailsService 的实现类,编写自定义认证逻辑

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UsersMapper usersMapper;
    // 自定义认证逻辑
    @Override
    public UserDetails
    loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.构造查询条件
        QueryWrapper wrapper = new QueryWrapper().eq("username", username);
        // 2.查询用户
        Users users = usersMapper.selectOne(wrapper);
        // 3.封装为UserDetails对象
        UserDetails userDetails = User
            .withUsername(users.getUsername())
            .password(users.getPassword())
            .authorities("admin")
            .build();
        // 4.返回封装好的UserDetails对象
        return userDetails;
    }
}
密码解析器PasswordEncoder

在数据库中存放密码时不会存放原密码,而是会存放加密后的密码。而用户传入的参数是明文密码。

Spring Security要求容器中必须有 PasswordEncoder 实例,之前使用的 NoOpPasswordEncoder 是 PasswordEncoder 的实现类,意思是不解析密码,使用明文密码。

Spring Security官方推荐的密码解析器是 BCryptPasswordEncoder 。

@SpringBootTest
public class PasswordEncoderTest {
    @Test
    public void testBCryptPasswordEncoder(){
        //创建解析器
        PasswordEncoder encoder = new BCryptPasswordEncoder();
            //密码加密
        String password = encoder.encode("baizhan");
        System.out.println("加密后:"+password);
        //密码校验
        
        boolean result = encoder.matches("baizhan","$2a$10$/MImcrpDO21HAP2amayhme8j2SM0YM50/WO8YBH.NC1hEGGSU9ByO");
        System.out.println(result);
    }
}

将 BCryptPasswordEncoder 的实例放入Spring容器即可,并且在用户注册完成后,将密码加密再保存到数据库

@Configuration
public class SecurityConfig {

    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
自定义登录界面

Spring Security也支持用户自定义登录页面

编写登录页面 在Spring Security配置类自定义登录页面
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    //Spring Security配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义表单登录
        http.formLogin().loginPage("https://blog.csdn.net/login.html") 
        // 自定义登录页面
        	.usernameParameter("username")
        // 表单中的用户名项
        	.passwordParameter("password")
        // 表单中的密码项
        	.loginProcessingUrl("/login")
        // 登录路径,表单向该路径提交,提交后自动执行UserDetailsService的方法
        	.successForwardUrl("/main")
        //登录成功后跳转的路径
        	.failureForwardUrl("/fail");
        //登录失败后跳转的路径
        
        // 需要认证的资源
        http.authorizeRequests()
            .antMatchers("https://blog.csdn.net/login.html").permitAll() 
        // 登录页不需要认证
        	.anyRequest().authenticated();
        //其余所有请求都需要认证
        //关闭csrf防护
        http.csrf().disable();
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 静态资源放行
        web.ignoring().antMatchers("/css
    private String pid;
    
    private String permissionName;
    
    private String url;
}

@Data
public class Role {
    
    private String rid;
    
    private String roleName;
    
    private String roleDesc;
}

@Data
public class Users {
    
    private Integer uid;
    
    private String username;
    
    private String password;
    
    private String phone;
}

UsersMapper.java

public interface UsersMapper extends BaseMapper {
    
    List findPermissionAllByUsername(@Param("username") String username);
}



    

修改自认证逻辑

@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private UsersMapper usersMapper;

    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.构造查询条件
        QueryWrapper wrapper = new QueryWrapper().eq("username", username);
        //2.查询结果
        Users users = usersMapper.selectOne(wrapper);
        if (users == null) {
            return null;
        }
        // 3.查询用户权限
        List permissions = usersMapper.findPermissionAllByUsername(username);
        // 4.将自定义权限集合转为Security的权限类型集合 List
        List grantedAuthority = new ArrayList<>();
        for (Permission permission : permissions) {
            grantedAuthority.add(new SimpleGrantedAuthority(permission.getUrl()));
        }
        // 5.封装为UserDetails对象
        UserDetails userDetails = User.withUsername(users.getUsername())
                                      .password(users.getPassword())
                                      .authorities(grantedAuthority)
                                      .build();
        // 6.返回对象
        return userDetails;
    }
}
配置类设置访问控制

由于没有权限,访问被拦截,就会抛出403异常。

编写控制器类,添加控制器方法资源

@RestController
public class MyController {

    @GetMapping("https://blog.csdn.net/reportform/find")
    public String findReportForm() {
        return "查询报表";
    }

    @GetMapping("https://blog.csdn.net/salary/find")
    public String findSalary() {
        return "查询工资";
    }

    @GetMapping("https://blog.csdn.net/staff/find")
    public String findStaff() {
        return "查询员工";
    }
}

修改Security配置类

// 权限拦截配置
http.authorizeRequests()
    // 表示任何权限都可以访问
    .antMatchers("https://blog.csdn.net/login.html").permitAll()
    // 给资源配置需要的权限
    .antMatchers("https://blog.csdn.net/reportform/find").hasAnyAuthority("https://blog.csdn.net/reportform/find")
    .antMatchers("https://blog.csdn.net/salary/find").hasAnyAuthority("https://blog.csdn.net/salary/find")
    .antMatchers("https://blog.csdn.net/staff/find").hasAnyAuthority("https://blog.csdn.net/staff/find")
    //表示任何请求都需要认证后才能访问
    .anyRequest().authenticated();

自定义访问控制逻辑

我们可以自定义访问控制逻辑,即访问资源时判断用户是否具有名为该资源URL的权限

自定义访问控制逻辑

@Service
public class MyAuthorizationService {
    
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        //获取会话中的登录用户
        Object principal = authentication.getPrincipal();
        if (principal instanceof UserDetails) {
            // 获取登录用户的权限
            Collection authorities = ((UserDetails) principal).getAuthorities();
            // 获取请求的URL路径
            String uri = request.getRequestURI();
            // 将URL路径封装为权限对象
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(uri);
            // 判断用户的权限集合是否包含请求的URL权限对象
            return authorities.contains(authority);
        }
        return false;
    }
}

在配置文件中使用自定义访问控制逻辑

// 权限拦截配置
http.authorizeRequests()
    // 表示任何权限都可以访问
    .antMatchers("https://blog.csdn.net/login.html").permitAll()
    // 任何请求都使用自定义访问控制逻辑
  .anyRequest().access("@myAuthorizationService.hasPermission(request,authentication)");
注解设置访问控制

除了配置类,在SpringSecurity中提供了一些访问控制的注解。这些注解默认都是不可用的,需要开启后使用。
@Secured 注解是基于角色的权限控制,要求 UserDetails 中的权限名必须以
ROLE_ 开头。

启动类

@SpringBootApplication
@MapperScan("com.doublew2w.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

在控制器上添加注解

@Secured("ROLE_reportform")
@GetMapping("https://blog.csdn.net/reportform/find")
public String findReportForm() {
	return "查询报表";
}

不过该注解一般很少用,常用的是下面的注解 @PreAuthorize

// 权限拦截配置
http.authorizeRequests()
    // 表示任何权限都可以访问
    .antMatchers("https://blog.csdn.net/login.html").permitAll()
    .antMatchers("/fail").permitAll()
    .anyRequest().authenticated();

@PreAuthorize 注解可以在方法执行前判断用户是否具有权限

@SpringBootApplication
@MapperScan("com.doublew2w.mapper")
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
@RestController
public class MyController {
    @PreAuthorize("hasAnyAuthority('https://blog.csdn.net/reportform/find')")
    @GetMapping("https://blog.csdn.net/reportform/find")
    public String findReportForm() {
        return "查询报表";
    }

    @PreAuthorize("hasAnyAuthority('https://blog.csdn.net/salary/find')")
    @GetMapping("https://blog.csdn.net/salary/find")
    public String findSalary() {
        return "查询工资";
    }

    @PreAuthorize("hasAnyAuthority('https://blog.csdn.net/staff/find')")
    @GetMapping("https://blog.csdn.net/staff/find")
    public String findStaff() {
        return "查询员工";
    }
}
在前端进行访问控制

当没有对应权限的时候,就没显示该菜单

引入依赖



    org.thymeleaf.extras
    thymeleaf-extras-springsecurity5

修改main.html




    
    
    
    
    登录首页


主页面

退出登录

编写当返回403的时候,处理页面




    
    权限不足


您的权限不足,请联系管理员!

编写权限不足处理类并配置

public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.sendRedirect("/noPermission.html");
    }
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private PersistentTokenRepository persistentTokenRepository;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义表单登录
        http.formLogin()
            // 自定义登录页面
            .loginPage("https://blog.csdn.net/login.html")
            // 登录路径,表单向该路径提交,提交后自动执行UserDetailsService方法
            .loginProcessingUrl("/login")
            // 表单中的用户名项
            .usernameParameter("username")
            // 表单中的密码项
            .passwordParameter("password")
            // 登录成功处理器
            .successHandler(new MyLoginSuccessHandler())
            // 登录失败处理器
            .failureHandler(new MyLoginFailureHandler());

        // 关闭csrf防护
        http.csrf().disable();

        // 退出登录配置
        http.logout()
            // 登出路径
            .logoutUrl("https://blog.csdn.net/logout")
            // 登出成功后请求的页面
            //.logoutSuccessUrl("https://blog.csdn.net/login.html")
            .logoutSuccessHandler(new MyLogoutSuccessHandler())
            //清除认证状 态,默认为true
            .clearAuthentication(true)
            // 销毁HttpSession对象,默认为true
            .invalidateHttpSession(true);

        // 记住我配置
        http.rememberMe()
            // 登录逻辑交给哪个对象
            .userDetailsService(userDetailsService)
            // 持久层对象
            .tokenRepository(persistentTokenRepository)
            // 保存时间(秒)
            .tokenValiditySeconds(30);

        // 权限拦截配置
        http.authorizeRequests()
            // 表示任何权限都可以访问
            .antMatchers("https://blog.csdn.net/login.html").permitAll()
            .antMatchers("/fail").permitAll()
            .anyRequest().authenticated();

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

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 静态资源放行
        web.ignoring().antMatchers("/css
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
总结

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/854843.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号