使用Spring Security进行Spring MVC的权限验证

https://www.tianmaying.com/tutorial/spring-security

Spring Security,这是一种基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring Framework基础上,Spring Security充分利用了依赖注入(DI,Dependency Injection)和面向切面技术。今天我们就来学习一下如何利用Spring security方便的进行权限验证。

添加依赖

首先还是先定义我们所需要的依赖:

 

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
<dependency>
        <groupId>org.hsqldb</groupId>
        <artifactId>hsqldb</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

在本次学习中,我们仍然使用Spring Boot作为框架,同时引入我们的核心学习库:spring-boot-starter-security。模板引擎仍然选用我们熟悉的thymeleaf。最后使用了Spring Data Jpa+ hsqldb作为数据库。

系统设计

本次学习,我们将实现一个简单的有权限验证的系统,在权限验证上,我们还可以加上一个简单的用户角色判断的验证,这样,我们就有以下几个页面:

  • 首页 所有人可访问
  • 登录页 所有人可访问
  • 普通页 登录后的用户都可访问
  • 管理页 管理员可访问
  • 无权限提醒页 当一个用户访问了其没有权限的页面,需要有一个页面来对其进行提醒

页面确定后,我们来看看用户表需要哪些字段。为了简化处理,我们的用户表只需要用户名、密码以及一个角色字段来确定其身份即可。

这样,我们的简单系统设计就出来了,为了加深印象,我们先不管权限验证相关代码,我们先来实现整个简单的系统吧。

系统实现

数据访问层

首先我们需要一个数据层以及业务层为我们的业务逻辑提供支持,由于我们没有业务逻辑,业务逻辑层就省去了,只需要利用Spring Data JPA来实现一个简单数据访问层即可。

首先来定义一个User对象,设置用户名、密码、角色三个字段:

 

@Entity
public class User {

    @Enumerated(EnumType.STRING)
    private ROLE role;

    @Id
    private String username;

    private String password;

    ...

}

然后定义UserRepository:

 

public interface UserRepository extends JpaRepository<User, Integer> {

    User findByUsername(String username);

}

这样,数据访问层就实现好了。

展现层

在展现层,我们需要提供系统设计中的所有页面。基于Spring MVC的Controller代码如下:

 

@Controller
public class HomeController {

    @RequestMapping(value = {"", "/home"}, method=RequestMethod.GET)
    public String home(){
        return "home";
    }

    @RequestMapping(value = "/helloadmin", method=RequestMethod.GET)
    public String helloAdmin(){
        return "helloAdmin";
    }

    @RequestMapping(value = "/hellouser", method=RequestMethod.GET)
    public String helloUser(){
        return "helloUser";
    }

    @RequestMapping(value = "/login", method=RequestMethod.GET)
    public String login(){
        return "login";
    }

    @RequestMapping("/403")
    public String forbidden(){
        return "403";
    }

}

注:其中返回的页面在src/main/resources/templates下,都是最简单的HTML页面,这里不在赘述。

 

关于Spring MVC的介绍情参考:Spring MVC快速入门

 

大家可以看到,最后一个403页面实际上应该是当用户访问了没有权限的页面后显示的页面,因此在这里我们需要设置:当发现请求的返回码是403时,需要交给'/403'进行处理。

 

@Configuration
public class ErrorPageConfig {

    @Bean
    public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
        return new MyCustomizer();
    }

    private static class MyCustomizer implements EmbeddedServletContainerCustomizer {

        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            container.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN, "/403"));
        }

    }

}

这里我们利用了Spring自带的EmbeddedServletContainerCustomizer进行设置。当Spring发现有类型为EmbeddedServletContainerCustomizer注册进来,便会调用EmbeddedServletContainerCustomizer的customize方法,此时,我们可以对整个Container进行设置,这里,我们添加了对于返回值为HttpStatus.FORBIDDEN的请求,将其交给/403进行处理。

启动程序

当然,我们还需要添加一个系统的启动函数,这个大家都应该很熟悉了,没有用过Spring Boot的同学可以参考教材Spring Boot应用开发

 

@SpringBootApplication
public class App {

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

初始化数据

最后,让我们准备一些测试数据:

 

@Service
public class DataInit {

    @Autowired UserRepository userRepository;

    @PostConstruct
    public void dataInit(){
        User admin = new User();
        admin.setPassword("admin");
        admin.setUsername("admin");
        admin.setRole(User.ROLE.admin);
        userRepository.save(admin);

        User user = new User();
        user.setPassword("user");
        user.setUsername("user");
        user.setRole(User.ROLE.user);
        userRepository.save(user);
    }

}

现在,一切准备就绪,进入根目录,运行mvn spring-boot:run,访问以下网页即可:

添加权限验证

上节内容我们搭建了一个简单的网页应用,但是其中完全没有任何登录、登出、权限验证等等的处理,只能保证每个页面的简单访问。接下来我们就来用Spring Security添加这些功能。

添加Spring Security配置

我们首先使用Spring Security帮我们做登录、登出的处理,以及当用户未登录时只能访问http://localhost:8080/以及http://localhost:8080/login两个页面。

从数据库中获取用户信息的操作是必不可少的,我们首先来实现UserDetailsService,这个接口需要我们实现一个方法:loadUserByUsername。即通过用户名加载与该用户的用户名、密码以及权限相关的信息。

 

public class CustomUserDetailsService implements UserDetailsService {

    @Autowired UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if(user == null){
            throw new UsernameNotFoundException("not found");
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(user.getRole().name()));
        System.err.println("username is " + username + ", " + user.getRole().name());
        return new org.springframework.security.core.userdetails.User(user.getUsername(),
                user.getPassword(), authorities);
    }

}

然后我们添加一个SecurityConfig类来对Security进行配置,使AuthenticationManager使用我们的CustomUserDetailsService来获取用户信息:

 

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        return new CustomUserDetailsService();
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/helloadmin")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

}

我们可以看到,SecurityConfig上添加了@EnableWebMvcSecurity标注,使得Spring Security提供并且支持了Spring MVC的集成。

同时,SecurityConfig还继承了WebSecurityConfigurerAdapter类,并覆盖了其中的几个方法:

  • userDetailsService()将我们自定义的CustomUserDetailsService实例化并添加进security的上下文当中
  • configure(AuthenticationManagerBuilder auth) 在该方法中,我们使AuthenticationManager使用CustomUserDetailsService作为其UserDetailsService实例。
  • configure(HttpSecurity http) 对URL进行权限配置,使得"/", "/home"不需要登录就可以访问,其他需要登录。登录的地址是'/login',当登录成功后将跳转到/helloadmin页面,并且登录、登出页面都是不需要登录就可以访问的。

这时候重启我们的应用,再次访问http://localhost:8080/hellouser,你会发现网页会自动跳转到登录页面,同时,我们的登录、登出功能已经实现好了。

实现一个UserDetailsService,再对Spring Security进行配置,这样,登录、登出以及登录的验证功能就可以实现啦,怎么样,是不是特别的简单。

添加角色验证

之前系统设计时,我们将用户分成了管理员与普通用户,那么,如何将这两类用户访问的页面分开,使得普通用户看不见管理员相关的页面呢?很简单,我们可以通过两行标注来实现这个功能,修改HomeController

 

@RequestMapping(value = "/helloadmin", method=RequestMethod.GET)
@PreAuthorize("hasAnyRole('admin')")
public String helloAdmin(){
    return "helloAdmin";
}

@RequestMapping(value = "/hellouser", method=RequestMethod.GET)
@PreAuthorize("hasAnyRole('admin', 'user')")
public String helloUser(){
    return "helloUser";
}

修改helloAdminhelloUser方法,添加@PreAuthorize标注,当我们访问这两个URL的时候,Spring Security会帮我们验证当前用户是否有权限访问该地址。

现在重新启动服务,用user账号登陆,访问'http://localhost:8080/helloadmin',怎么样,是不是看见403页面啦。添加角色控制,就是这么简单。

猜你喜欢

转载自nethub2.iteye.com/blog/2348422