SpringSecurity详解

2023-10-31

一、Spring Security简介

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。Spring Security致力于为Java应用程序提供身份验证和授权的能力。像所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足定制需求的能力。

Spring Security两大重要核心功能:用户认证(Authentication)用户授权(Authorization)

  • 用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。

  • 用户授权:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,有的用户既能读取,又能修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

二、快速开始

使用Springboot工程搭建Spring Security项目。

1.引入依赖

<dependencies>
        <!--————springSecurity的依赖————-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
​
        <!--————— MySql—————-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
​
        <!--————— mybatis-plus插件依赖 —————-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
​
        <!--————— mybatis-plus代码生产器 —————-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.2</version>
        </dependency>
​
        <!--—————模板 代码生成器使用模板进行生产—————-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.7</version>
        </dependency>
​
        <!--—————配置类相关的依赖—————-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
​
        <!--—————mybatis-plus 扩展插件 比如 分页插件依赖—————-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.4.2</version>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
​
        <!--————thymeleaf的依赖————-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

在pom中新增了Spring Security的依赖

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2.创建测试访问接口

用于访问接口时触发Spring Security登陆页面

@RestController
public class SecurityController {
​
    @RequestMapping("/add")
    public String add(){
        return "hello security!";
    }
​
}

3.访问接口,自动跳转至Security登陆页面

通过浏览器输入框输入localhost:8080/add,将自动跳转至Security的登陆页面

默认账号是: user

默认密码是:启动项目的控制台中输出的密码

 三、原理剖析

在上面中访问add接口,发现被Spring Security的登陆页面拦截,可以猜到这是触发了Security框架的过滤器。Spring Security本质上就是一个过滤器链。下面讲介绍Security框架的过滤器链。

1.过滤器链

  • FilterSecurityInterceptor:是一个方法级的权限过滤器,位于过滤器链的最底部。

  • ExceptionTranslationFilter: 异常过滤器,用来处理在认证授权过程中抛出异常

  • UsernamePasswordAuthenticationFilter: 用于对/login的POST请求做拦截,校验表单中的用户名和密码。

2.过滤器加载过程

Springboot在整合Spring Security项目时会自动配置DelegatingFilterProxy过滤器,若非Springboot工程,则需要手动配置该过滤器。

过滤器如何进行加载的?

结合上图和源码,Security在DelegatingFilterProxy的doFilter()调用了initDelegat()方法,在该方法中调用了WebApplicationContext的getBean()方法,该方法出发FilterChainProxy的doFilterInternal方法,用于获取过滤链中的所有过滤器并进行加载。

3.Security的两个关键接口

在快速开始中发现Spring Security使用了默认的用户名和密码,实际用户名和密码需要自定义,因此会用到以下两个接口。下述两个接口的具体实现将在之后的例子中体现。

1) UserDetailsService接口

若需要从数据库中获取用户名和密码,则需要把查询数据库的过程写在这个接口里。

2)PasswordEncoder接口

在密码的处理上,需要进行编解码器,该接口实现对密码进行加密。

四、多种方式配置登陆的用户名和密码

1.通过配置文件设置用户名和密码

# 方式一:设置登陆的用户名和密码
spring:
  security:
    user:
      name: qfadmin
      password: 123456

2.通过创建配置类实现设置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
​
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //用于密码的密文处理
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //生成密文
        String password = passwordEncoder.encode("123456");
        //设置用户名和密码
        auth.inMemoryAuthentication().withUser("qfAdmin").password(password).roles("admin");
    }
  
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

3.编写自定义实现类(常用)

第一步:编写UserDetailsService实现类,可以从数据库中获取用户名和密码

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //设置角色,角色的概念在之后章节介绍
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        //可以从数据库获取用户名和密码,这里返回的是数据库中的数据,然后再去和用户输入的密码比较
        return new User("qfAdmin",new BCryptPasswordEncoder().encode("123456"),auths);
    }
}

第二步:编写配置类

@Configuration
public class SecurityConfigByImpl extends WebSecurityConfigurerAdapter {
​
    @Autowired
    private UserDetailsService userDetailsService;
​
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
​
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login.html") //设置自定义登陆页面,注意如果不通过controller跳转这个页面需要放在static中
                .loginProcessingUrl("/user/login") //登陆时访问的路径,也就是s这个路径表示表单提交,处理登录请求的controller不需要我们写,security帮我们做到了
                .defaultSuccessUrl("/index").permitAll() //登陆成功后跳转的路径
                .and().authorizeRequests()
                .antMatchers("/","/add","/user/login").permitAll() //设置可以直接访问的路径,取消拦截
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
    }
​
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

为了测试顺利,这里临时关闭csrf防护。所谓csrf防护,全称为跨站请求伪造(Cross-site request forgery),是一种网络攻击方式,CSRF攻击利用网站对于用户网页浏览器的信任,挟持用户当前已登陆的Web应用程序,去执行并非用户本意的操作。简而言之,用户通过盗取目标网站保存的cookie中的用户信息,实现非法使用。

其中,login.html为自己提供的登陆页面,具体内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/usr/login" method="post">
        用户名:<input type="text" name="username"/><br/>
        密码:<input type="password" name="password"><br/>
        <input type="submit" value="login"/>
    </form>
​
</body>
</html>

注意:表单提交的地址为配置类中配置的登陆时访问路径:/usr/login

第三步:在controller中添加/index接口

@RestController
public class SecurityController {
​
    @RequestMapping("/add")
    public String add(){
        return "hello security!";
    }
​
    @RequestMapping("/index") 
    public String index(){
        return "hello index";
    }
​
}

五、基于角色和权限进行访问控制

Spring Security提供了四个方法用于角色和权限的访问控制。通过这些方法,对用户是否具有某个或某些权限,进行过滤访问。对用户是否具备某个或某些角色,进行过滤访问。

1.hasAuthority方法

判断当前主题是否有指定的权限,有返回true,否则返回false

该方法适用于只拥有一个权限的用户

1)在配置类中设置当前主体具有怎样的权限才能访问。

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置没有权限的跳转页面
        http.exceptionHandling().accessDeniedPage("/error.html");
        http.formLogin()
                .loginPage("/login.html") //设置自定义登陆页面
                .loginProcessingUrl("/usr/login") //登陆时访问的路径
                .defaultSuccessUrl("/index").permitAll() //登陆成功后跳转的路径
                .and().authorizeRequests()
                    .antMatchers("/","/add","/user/login").permitAll() //设置可以直接访问的路径,取消拦截
                    //1.hasAuthority方法:当前登陆用户,只有具有admin权限才可以访问这个路径
                    .antMatchers("/index").hasAuthority("admin")
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
    }

2)在userdetailsService,为返回的User对象设置权限

 @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //因目前还没引入角色的概念,先用工具类快速生成角色
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        //可以从数据库获取用户名和密码
        return new User("qfAdmin",new BCryptPasswordEncoder().encode("123456"),auths);
    } 

2.hasAnyAuthority方法

适用于一个主体有多个权限的情况,多个权限用逗号隔开。

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        //注销的配置
        http.logout().logoutUrl("/logout") //注销时访问的路径
                .logoutSuccessUrl("/logoutSuccess").permitAll(); //注销成功后访问的路径
​
        //配置没有权限的跳转页面
        http.exceptionHandling().accessDeniedPage("/error.html");
        http.formLogin()
                .loginPage("/login.html") //设置自定义登陆页面
                .loginProcessingUrl("/usr/login") //登陆时访问的路径
//                .defaultSuccessUrl("/index").permitAll() //登陆成功后跳转的路径
                .defaultSuccessUrl("/success.html").permitAll() //登陆成功后跳转的路径
                .and().authorizeRequests()
                    .antMatchers("/","/add","/user/login").permitAll() //设置可以直接访问的路径,取消拦截
                    //2.hasAnyAuthority方法:当前登陆用户,具有admin或manager权限可以访问这个路径
     .antMatchers("/index").hasAnyAuthority("admin,manager")
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
    }

2)在userdetailsService,为返回的User对象设置权限

  @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //因目前还没引入角色的概念,先用工具类快速生成角色
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        //可以从数据库获取用户名和密码
        return new User("Admin",new BCryptPasswordEncoder().encode("123456"),auths);
    } 

3.hasRole方法

如果用户具备给定角色就允许访问,否则报403错误。

1)修改配置类

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        //注销的配置
        http.logout().logoutUrl("/logout") //注销时访问的路径
                .logoutSuccessUrl("/logoutSuccess").permitAll(); //注销成功后访问的路径
​
        //配置没有权限的跳转页面
        http.exceptionHandling().accessDeniedPage("/error.html");
        http.formLogin()
                .loginPage("/login.html") //设置自定义登陆页面
                .loginProcessingUrl("/usr/login") //登陆时访问的路径
//                .defaultSuccessUrl("/index").permitAll() //登陆成功后跳转的路径
                .defaultSuccessUrl("/success.html").permitAll() //登陆成功后跳转的路径
                .and().authorizeRequests()
                    .antMatchers("/","/add","/user/login").permitAll() //设置可以直接访问的路径,取消拦截
                    //3.hasRole方法:当前主体具有指定角色,则允许访问
                    .antMatchers("/index").hasRole("student")
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
    }

2)修改user对象

//权限设置
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //因目前还没引入角色的概念,先用工具类快速生成角色
        //hasRole:  由于源码会把role加上"ROLE_",因此在这里设计角色时需加上前缀
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_student");
        //可以从数据库获取用户名和密码
        return new User("qfAdmin",new BCryptPasswordEncoder().encode("123456"),auths);
    }

其中角色student需要在设置时加上“ROLE”前缀,因为通过源码hasRole方法给自定义的角色名前加上了“ROLE”前缀

private static String hasRole(String role) {
        Assert.notNull(role, "role cannot be null");
        Assert.isTrue(!role.startsWith("ROLE_"), () -> {
            return "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'";
        });
        return "hasRole('ROLE_" + role + "')";
    }

4.hasAnyRole方法

设置多个角色,多个角色之间使用逗号隔开,只要用户具有某一个角色,就能访问。

@Override
    protected void configure(HttpSecurity http) throws Exception {
        //注销的配置
        http.logout().logoutUrl("/logout") //注销时访问的路径
                .logoutSuccessUrl("/logoutSuccess").permitAll(); //注销成功后访问的路径
​
        //配置没有权限的跳转页面
        http.exceptionHandling().accessDeniedPage("/error.html");
        http.formLogin()
                .loginPage("/login.html") //设置自定义登陆页面
                .loginProcessingUrl("/usr/login") //登陆时访问的路径
//                .defaultSuccessUrl("/index").permitAll() //登陆成功后跳转的路径
                .defaultSuccessUrl("/success.html").permitAll() //登陆成功后跳转的路径
                .and().authorizeRequests()
                    .antMatchers("/","/add","/user/login").permitAll() //设置可以直接访问的路径,取消拦截
                    //4.hasAnyRole方法:当前主体只要具备其中某一个角色就能访问
                    .antMatchers("/index").hasAnyRole("student1,teacher")
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
    }

六、SpringSecurity的常用注解

1、@Secured注解

@Secured注解用于校验用户具有某个角色,才可以访问方法

1)启动类上开启注解

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityDemo1Application {
    public static void main(String[] args) {
       SpringApplication.run(SecurityDemo1Application.class, args);
    }
}

2)在方法上配置注解

 @RequestMapping("/admin")
    @Secured("ROLE_ls")
    public String testAuthority(){
        return "ls的角色";
    }

3)用户对象中设置角色

     @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_student");
        //可以从数据库获取用户名和密码
        return new User("qfAdmin",new BCryptPasswordEncoder().encode("123456"),auths);
    }

2、@PreAuthorize

进入方法前的权限验证

步骤

在启动类上开启注解

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

 在方法上使用注解

@RequestMapping("/items")
 @PreAuthorize("hasAnyAuthority('admin')")
 public String items(){
     return "show itemds";
 }

注意:方法参数是之前介绍的四个方法。

3、@PostAuthorize

在方法访问之后进行校验,实际使用并不多

步骤

启动类上开启注解

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

 方法上使用注解

@RequestMapping("/postItems")
    @PostAuthorize("hasAnyAuthority('teacher')")
    public String postItems(){
        //先执行方法内容,再做权限校验
        System.out.println("show detail here...");
        return "show post items";
    }

4、@PostFilter

权限验证之后对数据进行过滤,只能获取满足条件的数据

步骤

在方法上使用注解

@RequestMapping("/postFilterItems")
    @PreAuthorize("hasAnyAuthority('admin')")
    @PostFilter("filterObject.userName == 'xiaoming'")
    public List<User> getUsers(){
​
        ArrayList<User> list = new ArrayList<User>();
        list.add(new User(1L,"xiaowang"));
        list.add(new User(2L,"xiaoming"));
        return list;
    }

 访问接口,发现list集合中中获取了满足条件的xiaoming对象

5、@PreFilter

对传入方法的数据进行过滤

步骤

在方法上使用注解

@RequestMapping("/preFilterItems")
    @PreAuthorize("hasAnyAuthority('admin')")
    @PreFilter(value="filterObject.userName == 'xiaoming'")
    public List<User> getUsersByPreFilter(@RequestBody List<User> list){
        //只有userName是'xiaoming'的数据才会被传入
        list.forEach(t->{
            System.out.println(t.getUserName());
        });
        return list;
    }

 访问方法,发现只有userName是'xiaoming'的数据才会被传入

七、案例

1.从数据库查询角色或权限案例

1.1准备数据库:

用户表users:

角色表role:

  

权限表menu:

 用户和角色关联表role_user:

  权限和角色关联表role_menu:

1.2创建配置类继承WebSecurityConfigurerAdapter

用注解方式验证权限

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
​
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
​
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login.html") //设置自定义登陆页面,注意如果不通过controller跳转这个页面需要放在static中
                .loginProcessingUrl("/user/login") //登陆时访问的路径,也就是s这个路径表示表单提交,处理登录请求的controller不需要我们写,security帮我们做到了
                .defaultSuccessUrl("/main").permitAll() //登陆成功后跳转的路径
                .and().authorizeRequests()
                .antMatchers("/","/add","/user/login").permitAll() //设置可以直接访问的路径,取消拦截
           .antMatchers("/main","/users/manage").hasAnyRole("普通用户")
​
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
    }
​
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
​

1.3创建application.yml配置文件

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/bank?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: root
    password: 123456
​
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看sql输出日志
​
  global-config:
    db-config:
      id-type: auto # id自增
  mapper-locations: classpath:mapper/*.xml #设置
​
  thymeleaf:
    cache: false
  type-aliases-package: com.ymk.securitydemo1.pojo #设置别名
​
logging:
  level:
    com.ymk.securitydemo1: debug
​

1.4创UsersMapper和UsersMapper.xml

@Repository
public interface UsersMapper extends BaseMapper<Users> {
    //根据用户id查询用户角色
    List<Role> selectRoleByUserId(Integer userId);
    
    //根据用户id查询权限
    List<Menu> selectMenuByUserId(Integer userId);
}
<mapper namespace="com.ymk.securitydemo1.mapper.UsersMapper">
    <!--根据用户id查询用户角色-->
    <select id="selectRoleByUserId" resultType="Role">
        select role.name
        from users,
             role_user r,
             role
        where users.id = r.uid
          and r.rid = role.id
          and users.id = #{id}
    </select>
    <!--根据用户id查询用户权限-->
    <select id="selectMenuByUserId" resultType="Menu">
        select menu.name,menu.permission
        from users,
             role_user r,
             role,
             role_menu m,
             menu
        where users.id = r.uid
          and r.rid = role.id
          and role.id = m.rid
          and m.mid = menu.id
          and users.id = #{id}
    </select>
</mapper>

1.5编写实现类UserDetailsService

在此类进行数据库查询

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
​
    @Autowired
    private UsersMapper usersMapper;
    // 接受传过来的用户名 根据传过来的用户名查询数据库的密码,返回数据库中的用户名、密码、权限信息
​
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询数据库
        QueryWrapper<Users> queryWrapper1 = new QueryWrapper<>();
        queryWrapper1.eq("username", username);
        Users users = usersMapper.selectOne(queryWrapper1);
        if (users == null) {
            throw new UsernameNotFoundException("用户名不存在!");
        }
​
        System.out.println(users);
        // 这个集合是 保存 用户的权限和角色的集合
//        List<GrantedAuthority> auths =
//                AuthorityUtils.commaSeparatedStringToAuthorityList("manger,ROLE_admin");
​
        //查询权限和角色,然后封装到User中
        ArrayList<GrantedAuthority> auths = new ArrayList<>();
​
        //查询用户角色的列表
        List<Role> roles = usersMapper.selectRoleByUserId(users.getId().intValue());
​
        //查询权限的列表
        List<Menu> menus = usersMapper.selectMenuByUserId(users.getId().intValue());
​
        //处理角色 拼接 ROLE_xxx
        for (Role role : roles) {
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_" + role.getName());
            auths.add(simpleGrantedAuthority);
        }
        //处理权限
        for (Menu menu : menus) {
            auths.add(new SimpleGrantedAuthority(menu.getPermission()));
        }
​
        System.out.println(auths);
        return new User(users.getUsername(), users.getPassword(), auths);
    }
}
​

1.6创建UserController类

是对用户登录时进行的操作,此时不用谢UserService和UserServiceImpl

@RequestMapping("/users")
@RestController
public class  UsersController {
​
    @Autowired
    private UsersService usersService;
​
    @RequestMapping("/admin")
    @Secured("ROLE_管理员") // 需要的角色
    public String testAuthority(){
        return "张三的角色";
    }
​
    @PreAuthorize("hasAnyAuthority('menu:user')") //需要的权限
    @RequestMapping("/manage")
    public String testAuthority2(){
        return "李四的角色";
    }
​
}

八、用户注销

1.在配置类添加注销的配置

@Override
    protected void configure(HttpSecurity http) throws Exception {
        //注销的配置
        http.logout().logoutUrl("/logout") //注销时访问的路径
                .logoutSuccessUrl("/logoutSuccess").permitAll(); //注销成功后访问的路径
​
        //配置没有权限的跳转页面
        http.exceptionHandling().accessDeniedPage("/error.html");
        http.formLogin()
                .loginPage("/login.html") //设置自定义登陆页面
                .loginProcessingUrl("/usr/login") //登陆时访问的路径
//                .defaultSuccessUrl("/index").permitAll() //登陆成功后跳转的路径
                .defaultSuccessUrl("/success.html").permitAll() //登陆成功后跳转的路径
                .and().authorizeRequests()
                    .antMatchers("/","/add","/user/login").permitAll() //设置可以直接访问的路径,取消拦截
                    //1.hasAuthority方法:当前登陆用户,只有具有admin权限才可以访问这个路径
                    //.antMatchers("/index").hasAuthority("admin")
                    //2.hasAnyAuthority方法:当前登陆用户,具有admin或manager权限可以访问这个路径
                    //.antMatchers("/index").hasAnyAuthority("admin,manager")
                    //3.hasRole方法:当前主体具有指定角色,则允许访问
                    //.antMatchers("/index").hasRole("student")
                    //4.hasAnyRole方法:当前主体只要具备其中某一个角色就能访问
                    .antMatchers("/index").hasAnyRole("student1,teacher")
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
    }

2.设置注销链接

添加success.html页面作为登陆成功后的跳转页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    登陆成功 <a href="/logout">退出</a>
</body>
</html>

登陆后访问退出按钮,实现注销功能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringSecurity详解 的相关文章

  • 如何迭代所有注册表项?

    我正在尝试迭代所有注册表项以查找 包含 并删除 jre1 5 0 14 值 有办法做到吗 下面的代码只是在特定键下找到jre1 5 0 14 我确实想迭代所有的键 顺便说一句 if 子句获取是否等于 jre1 5 0 14 但如果它包含 j
  • 如何在Java 8中实现Elvis运算符?

    我有一个经典的 Elvis 运算符 案例 其中我调用每个可能返回 null 的方法并将它们链接在一起 thing nullableMethod1 a nullableMethod2 b nullableMethod3 在 Java 8 中
  • 使用 AbstractTableModel 获取 JTable 中选定的行

    我有一个JTable using AbstractTableModel我在哪里有一个JCheckBox在第一列中用于选择行 现在 我需要从已检查的表中获取选定的行 现在 我按顺序从第一行遍历到最后一行并获取所有选择的行 如下所示 List
  • Javadoc 1.5 和 1.6 中缺少 enum.valueOf(String name)

    这可能是一个愚蠢的问题 但我正在使用该方法enum valueOf String name 那里没问题 只是当我检查 javadoc 以了解有关此方法的更多信息时 我找不到它 有javadoc用于valueOf Class
  • 静态方法的 Java 内存模型

    我来自操作系统和 C 语言背景 在代码编译时 世界很简单 需要处理和理解堆栈 堆文本部分等 当我开始学习 Java 时 我确实了解 JVM 和垃圾收集器 我对静态方法感到很有趣 根据我的理解 类的所有实例都会在堆中创建 然后被清理 但是 对
  • 使用 Spring Data REST 处理自定义异常 (i18n)

    我正在使用 Spring Boot 1 5 4 和 Spring JPA Spring Data REST HATEOAS 我正在寻找一种最佳实践 Spring 方式 来自定义异常 Spring Data REST 正在管理添加 i18n
  • Spring-boot中将redis-cache反序列化为对象的问题

    我在 Client 类中使用 JsonNode 来处理 MySQL 8 数据库中 JSON 类型的字段 即使对于 API 请求 它也能很好地工作 但是当我使用 Redis 启用缓存 我确实需要它 时 我注意到 Redis 无法序列化 Jso
  • 在 TestNG 中运行多个类

    我正在尝试自动化一个场景 其中我想登录一次应用程序 然后进行操作而无需再次重新登录 考虑一下 我有在特定类的 BeforeSuite 方法中登录应用程序的代码 public class TestNGClass1 public static
  • Java Spring 应用程序存在内存泄漏。系统非堆内存不断增加

    我已使用 yourkit 分析器监视我的 Web 应用程序 保留最大大小的主要对象是 SessionFactoryImpl webappclassloader 和 CGlib 对象显示 spring crone调度程序会导致内存泄漏吗 我尝
  • 在 Hibernate 中创建 UPDATE RETURNING 查询

    在 Oracle 中 我们可以创建一个更新查询 该查询将使用 RETURNING 子句返回更新的记录 Hibernate中有类似的功能吗 除了数据库生成的值之外 Hibernate 显然不需要返回更新的实例 因为对象传递给Session s
  • SimpleDateFormat 将 lenient 设置为 false 时出现异常

    为什么这段代码会抛出无法解析日期的异常 SimpleDateFormat f new SimpleDateFormat yyyy MM dd T HH mm ss 000Z f setLenient false String dateStr
  • 如何通过子 POJO 的属性过滤复合 ManyToMany POJO?

    我有两个像这样的房间实体 Entity public class Teacher implements Serializable PrimaryKey autoGenerate true public int id ColumnInfo n
  • Java ConcurrentModificationException [重复]

    这个问题在这里已经有答案了 当删除倒数第二个元素时 没有 ConcurrentModificationException List
  • 在Java中多次读取System.in会导致IOException?

    我正在尝试创建一个小命令行游戏来强化我在过去几个月中在 Java 中学到的一些东西 我正在尝试创建一个名为 readInput 的方法 它返回一个我可以一次又一次调用的字符串 第一次它工作正常 但第二次它会导致 IO Exception 如
  • 如何使用云打印打印Android活动显示

    我正在尝试将 Google 云打印实现到应用程序中 遵循集成指南 https developers google com cloud print docs android 我试图通过打印 google com 来保持基本 单击我创建的打印按
  • scala中的协变类型参数需要在java接口中保持不变

    我有一个看起来像这样的特征 一些进一步的信息可以在我自己提出了这个相关问题 https stackoverflow com questions 3695990 inheritance and automatic type conversio
  • Google Cloud Messaging - 立即收到或长时间延迟收到的消息

    我在大学最后一年的项目中使用谷歌云消息传递 一切正常 但我在使用 GCM 时遇到了一些麻烦 通常 消息要么几乎立即传递 要么有很大的延迟 我读过这篇文章 但我真的认为它不适用于这种情况 GCM 通常会在消息发送后立即传送消息 然而 这并不总
  • 如何从spark中的hbase表中获取所有数据

    我在 hbase 中有一个大表 名称为 UserAction 它具有三个列族 歌曲 专辑 歌手 我需要从 歌曲 列族中获取所有数据作为 JavaRDD 对象 我尝试了这段代码 但效率不高 有更好的解决方案来做到这一点吗 static Spa
  • 如何使用剪辑来减少绘画时间?

    我正在尝试使用 Clip 来减少 CPU 负载 但剪辑在屏幕上留下了一些我似乎无法摆脱的垃圾 另外 打开和关闭剪辑似乎对 CPU 负载没有影响 在任一情况下 大部分时间似乎都花在重绘管理器和绘制缓冲图像上 import static jav
  • 我找不到 IntelliJ 快捷方式

    我使用 vim 一段时间 我知道有一个 intellij vim 插件 我很好奇内置的 IntelliJ 文本导航存在什么 如何打开实时模板来创建模板 如何查看以 tr 开头的现有模板列表 如何进行全局搜索并在当前文档中进行搜索 然后转到下

随机推荐

  • 《Deep Residual Learning for Image Recognition》论文学习

    Deep Residual Learning for Image Recognition 文章地址 Deep Residual Learning for Image Recognition arXiv 1512 03385 ResNet G
  • 【华为OD统一考试B卷

    在线OJ 已购买本专栏用户 请私信博主开通账号 在线刷题 运行出现 Runtime Error 0Aborted 请忽略 华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一
  • 利用SpringBoot简单快速搭建WEB项目入门

    我们在WEB应用开发过程中 我们常常用到的语言是JAVA语言 作为WEB应用中的霸主级别中 的存在 却常常被嘲讽 人生苦短 我用python 是python程序员们引以为傲的格言 人们经常 将JAVA与时代新宠Python作比较 至今网上还
  • 用Python每天自动给女朋友免费发短信,谁说程序员不懂浪漫?

    前言 之前发过一篇文章 用 Python 制作的给父母天气预报提醒的小工具天气变冷了 给父母制作一个天气提醒小助手 这篇文章我同步到博客上之后 有读者在评论区留言 对于部分微信没有网页版接口 导致无法实现这个功能 这位读者建议 建议用发短信
  • Linux自动删除tomcat日志文件

    查看Linux启动的所有crontab crontab l 编辑crontab crontab e bin sh export LANG zh CN export WEB HOME webhome find dir path dir pru
  • 【渗透测试】Struts2系列漏洞

    目录 S2 001 1 漏洞原理 2 影响版本 3 验证方法 S2 005 1 漏洞原理 2 影响版本 3 验证方法 无回显 4 验证方法 有回显 S2 007 1 漏洞原理 2 影响版本 3 漏洞验证 S2 008 1 漏洞原理 2 影响
  • k8s 探针

    1 前言 Kubernetes探针 Probe 是用于检查容器运行状况的一种机制 探针可以检查容器是否正在运行 容器是否能够正常响应请求 以及容器内部的应用程序是否正常运行等 在Kubernetes中 探针可以用于确定容器的健康状态 如果容
  • CSDN账号等级提升规则

    2023最新 大家登录CSDN主要有两种需求 一是获取资源 另一种是发放资源 CSDN大约从2022年开始 不断调整等级规则 CSDN账号达到4级才能有资格上传付费资源 我搜索发现CSDN账号可能因实名的原因 没有买卖的 本文主要介绍3个内
  • 使用uni.share在IOS上分享不显示图片的问题

    问题背景 使用uni app跨平台 编译运行到ios上 发现分享图文的时候 图片无法显示 在安卓上分享正常 uni分享朋友圈api地址 uni share provider weixin scene WXSenceTimeline type
  • 寻找凸包

    问题 点集 Q 的凸包 convex hull 是一个最小的凸多边形 P Q 中的每个点或在 P 的边界上或 在 P 的内部 我们用 CH Q 表示点集 Q 的凸包 问题定义 输入 平面上的点集 Q 输出 Q 的凸包 CH Q a 请给出一
  • CreateEvent人工重置事件对象

    include
  • 基于springboot的在线考试系统

    本系统和现在有的考试系统有以下几种优势 a 和现在有的系统比较起来 本系统有科目 章节 老师 学生 班级等信息的管理 还有批阅试卷查看已批阅试卷等 传统的考试系统划分并不细 业务功能简单 b 和学校的考试系统还有外面的考试系统比较起来 本系
  • 【配置文档】配置使用CGAL库的经验分享

    诸多经验贴都建议参考CGAL官方网站的步骤一步一步配置 因为我懒得看英文所以找的都是中文博客和问答 结果走了不少弯路 这里开一篇经验贴 记录错误也方便以后的查阅 本文是基于VS2017的配置和使用 这里是CGAL的使用手册 文章目录 一 需
  • Python案例篇2-pycharm import cx_Oracle模块引发的No module named ‘custom_exceptions‘

    一 问题描述 最近在自学python 然后用到Oracle数据库 于是开始学习cx Oracle模块 代码 import cx Oracle dbConnect host cx Oracle makedsn mylocalhost mypo
  • Linux防火墙查看及白名单添加

    一 临时白名单添加 执行即生效 重启防火墙后失效 查看防火墙状态 service iptables status 查看白名单列表 sudo iptables nL 添加白名单 sudo iptables I INPUT m state st
  • Spring AOP 源码分析 - 拦截器链的执行过程

    1 简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章 在前面的两篇文章中 我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器 以及如何创建代理对象的过程 现在我们的得到了 bean 的代理对象 且通知也以合
  • java:无法从静态上下文中引用非静态方法

    编辑以下代码 public class t public int i public void fun public static void main String args i 3 fun 编译 javac t java 得到以下报错 原因
  • c++svd算法_2020DCIC智能算法赛智慧海洋建设TOP1方案

    大家好 我是来自团队Pursuing the Past Youth的Ethan 天池ID是GrandRookie 和队友青禹小生 wbbhcb Chauncy YAO经过2个多月的 征途 最终在本届智能算法赛部分拿到了线上Top1的成绩 下
  • 蓝桥杯第十届青少年Python组省赛试题

    ns 1 3 5 8 cnt 0 for a in ns for b in ns for c in ns if a b and a c and b c print a 100 b 10 c cnt 1 print cnt for i in
  • SpringSecurity详解

    一 Spring Security简介 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架 Spring Security致力于为Java应用程序提供身份验证和授权的能力 像所有Spring项目一样 Sprin