SpringSecurity基本操作

Spring Security

基本概念

认证

验证身份,比如访问网站或app的登录就是认证。

授权

用户认证后通过后,根据事先分配给用户的权限来控制来控制其访问系统的资源。用户有某个资源的权限可以访问,没有则访问不了。

集成SpringSecurity与登陆认证

  1. 添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        <version>2.7.3</version>
    </dependency>
    
  2. 继承Security配置类,继承WebSecurityConfigurerAdapter类,添加@EnableWebSecurity注解,重写configure(HttpSecurity http) ,如果使用thymeleaf模板引擎,需要导入依赖,不然跳转页面的时候会出错。

    注意:SpringBoot2.7 WebSecurityConfigurerAdapter类过期配置

    package com.example.springsecurity.config;
    
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    /**
     * @author Pillar
     * @version 1.0
     * @date 2022/9/13 12:07
     */
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin()//开启登陆
                .successForwardUrl("/index");//登陆成功后的页面
        }
    }
    
    @Controller
    public class IndexController {
        @RequestMapping("/index")
        public String Index(){
            return "index";
        }
    }
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
      Hello,Srping Sercurity
    <h1>
        <a href="/logout">退出登录</a>
    </h1>
    </body>
    </html>
    

    测试结果:根据用户名user和默认生成的密码可以进行登录和退出,但是退出之后,还是可以访问index页面,怎么样才能未登录不能访问主页呢

  3. 添加.authorizeRequests().anyRequest().authenticated(); ,不登录访问index,直接跳转到登陆页面

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
            formLogin()//
            .successForwardUrl("/index")//登陆成功后的页面
            .and()
            .authorizeRequests().anyRequest().authenticated();//认证请求中所有请求都需要被认证
    }
    
  4. 添加账号,重写configure(AuthenticationManagerBuilder auth) ,一般从数据库中读取,先测试,先从内存中创建,并且设置无加密,并创建用户

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .passwordEncoder(NoOpPasswordEncoder.getInstance())
            .withUser("admin").password("admin").roles("admin")
            .and()
            .withUser("mqk").password("mqk").roles("user");
    }
    
  5. 使用BCrypt加密,先生成加密

    public class BCryptTest {
        private static final String SALT = "$2a$10$iu37mYcqBylrMgloNl33A.";
        public static void main(String[] args) {
            //也可以使用自动生成的盐值  BCrypt.gensalt()
    //        System.out.println(BCrypt.gensalt());
            System.out.println(BCrypt.hashpw("admin", SALT));
            System.out.println(BCrypt.hashpw("mqk", SALT));
        }
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password("$2a$10$iu37mYcqBylrMgloNl33A.nYpadegA6oRObaQhAzVMLi23knSfLc2").roles("admin")
                .and()
                .withUser("mqk").password("$2a$10$iu37mYcqBylrMgloNl33A.hQFKzxTDrEwemaOZT2duk/KURgpH9XG").roles("user");
    }
    

URL访问控制

对应角色只能访问对应资源

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.
        formLogin()//
        .successForwardUrl("/index")//登陆成功后的页面
        .and()
        .authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")//admin目录下的所有请求都能被admin角色访问
        .antMatchers("/user").hasRole("user")//user请求只能被user角色访问
        .antMatchers("/**/*.jpg","/**/*.png").hasRole("user")//所有png和jpg图片请求只能被user角色访问
        .anyRequest().authenticated();//认证请求中所有请求都需要被认证
}

ANT通配符有三种:
? 匹配任何单字符
* 匹配0或者任意数量的字符
** 匹配0或者更多的目录

角色和授权

看源码

对于角色来说,它的最终结果还是添加权限

public User.UserBuilder roles(String... roles) {
    List<GrantedAuthority> authorities = new ArrayList(roles.length);
    String[] var3 = roles;
    int var4 = roles.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        String role = var3[var5];
        Assert.isTrue(!role.startsWith("ROLE_"), () -> {
            return role + " cannot start with ROLE_ (it is automatically added)";
        });
        authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
    }

    return this.authorities((Collection)authorities);
}

在存储级别根本没有角色概念

public static List<GrantedAuthority> createAuthorityList(String... authorities) {
    List<GrantedAuthority> grantedAuthorities = new ArrayList(authorities.length);
    String[] var2 = authorities;
    int var3 = authorities.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        String authority = var2[var4];
        grantedAuthorities.add(new SimpleGrantedAuthority(authority));
    }
    return grantedAuthorities;
}

权限的使用authorities("img"); hasAnyAuthority("img")

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .passwordEncoder(new BCryptPasswordEncoder())
        .withUser("admin").password("$2a$10$iu37mYcqBylrMgloNl33A.nYpadegA6oRObaQhAzVMLi23knSfLc2").roles("admin")
        .and()
        .withUser("mqk").password("$2a$10$iu37mYcqBylrMgloNl33A.hQFKzxTDrEwemaOZT2duk/KURgpH9XG").authorities("img");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.
        formLogin()//
        .successForwardUrl("/index")//登陆成功后的页面
        .and()
        .authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")//admin目录下的所有请求都能被admin角色访问
        .antMatchers("/user").hasRole("user")//user请求只能被user角色访问
        .antMatchers("/**/*.jpg","/**/*.png").hasAnyAuthority("img")//所有png图片请求只能被user角色访问
        .anyRequest().authenticated();//认证请求中所有请求都需要被认证
}

连接数据库登陆认证

  1. 先准备好根据用户名查询用户信息的mapper方法

数据库

image-20220913191952158

实体类

@Data
public class UserInfo {
    private Integer Id;
    private String Username;
    private String password;
    private String role;
}

整合Mybatis

https://www.cnblogs.com/do-it-520/p/springboot_mybatis.html

https://www.cnblogs.com/do-it-520/p/16580064.html

编写mapper

package com.example.springsecurity.mapper;

import com.example.springsecurity.vo.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

/**
 * @author Pillar
 * @version 1.0
 * @date 2022/9/13 18:49
 */
@Mapper
@Repository
public interface UserInfoMapper {
    UserInfo selectByUsername(String username);
}

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.example.springsecurity.mapper.UserInfoMapper">
    <resultMap id="userResultMap" type="com.example.springsecurity.vo.UserInfo">
    </resultMap>
    <select id="selectByUsername" resultMap="userResultMap">
        select * from userInfo where username = #{username}
    </select>
</mapper>
  1. 实现UserDetailsService
@Service
public class UserService implements UserDetailsService {
    @Autowired
    private UserInfoMapper mapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo userInfo = mapper.selectByUsername(username);
        return User.withUsername(userInfo.getUsername()).password(userInfo.getPassword()).roles(userInfo.getRole()).build();
    }
}
  1. 修改配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserService userService;
        ***
        ***
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }
}

自定义登陆界面

login_view.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="login" method="post">
    <div>账号:<input type="text" name="username"></div>
    <div>密码:<input type="password" name="password"></div>
    <div><input type="submit" value="登陆"></div>
</form>
</body>
</html>

controller

@RequestMapping("/login_view")
public String Login(){
    return "login_view";
}

config

CSRF是指跨站请求伪造(Cross-site request forgery),在项目中添加了Security模块后,在发送post请求时会失败,出现以下日志:Invalid CSRF token found for ...

原因 在Security的默认拦截器里,默认会开启CSRF处理,判断请求是否携带了token,如果没有就拒绝访问。并且,在请求为(GET|HEAD|TRACE|OPTIONS)时,则不会开启。

解决 手动关闭csrf

.loginPage("/login_view").permitAll() 需要为登录界面开放权限

.loginProcessingUrl("/login") 登陆请求地址

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf().disable()//
            .formLogin()//
            .loginPage("/login_view").permitAll()//为登陆页面开放权限,无论是谁都可以登录
            .loginProcessingUrl("/login")//登陆请求地址
            .successForwardUrl("/index")//登陆成功后的页面
    .and()
            .authorizeRequests()
            .antMatchers("/admin/**").hasRole("admin")//admin目录下的所有请求都能被admin角色访问
            .antMatchers("/user").hasRole("user")//user请求只能被user角色访问
            .antMatchers("/**/*.jpg","/**/*.png").hasRole("user")//所有png图片请求只能被user角色访问
            .anyRequest().authenticated();//认证请求中所有请求都需要被认证
}

继承Thymeleaf

image-20220913211344317

image-20220913213223440

<!DOCTYPE html>
<!--导入命名空间-->
<html lang="en"  xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  Hello,Srping Sercurity

<div sec:authorize="isAuthenticated()">
    <h1>
        <a class="item" th:href="@{/logout}">
            <i class="sign-out icon"></i> 注销
        </a>
    </h1>
</div>
<!--所有的html元素都可以被thymeleaf接管 th:元素名-->
  <!--如果未登录-->
  <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>
</body>
</html>

登陆过期时间设置

server:
    servlet:
      session:
        timeout: 最低60秒,低于60按60处理。默认1800秒,就是30分钟

设置超时的特定页面

 @Override
protected void configure(HttpSecurity http) throws Exception {
    .and()
        .sessionManagement()
        .invalidSessionUrl("/timeout");
}

记住我

image-20220913222821064

@Override
protected void configure(HttpSecurity http) throws Exception {
    .and()
        .rememberMe()
        .tokenValiditySeconds(2000);
}

image-20220913224647730

                .and()
                .rememberMe()
                .tokenValiditySeconds(2000)
                .tokenRepository(persistentTokenRepository());
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
    tokenRepository.setDataSource(dataSource);
    //tokenRepository.setCreateTableOnStartup(true);
    return tokenRepository;
}

根据setCreateTableOnStartup中init方法手动创建数据库表,因为init方法为保护方法,不继承用不了,所以直接将setCreateTableOnStartup(true)注释掉。

登陆完之后

image-20220913225848235

posted @   NeverLateThanBetter  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· 10亿数据,如何做迁移?
· 推荐几款开源且免费的 .NET MAUI 组件库
· c# 半导体/led行业 晶圆片WaferMap实现 map图实现入门篇
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
点击右上角即可分享
微信分享提示