1.Spring Boot Security简介
Spring Security是一個安全组件。Spring Security采用“安全层”概念使每一层都尽可能安全。Spring Security可以在Controller层、service层、Dao层等通过加注解的方式来保护应用程序的安全。
在安全方面,有两个主要的领域:认证和授权。认证是指认证主体的过程,通常是指可以在应用程序中执行操作的用户、设备或其他系统;授权是指决定是否允许已认证的主题执行某一操作
Spring Security框架中,主要包含2个依赖jar:spring-security-web和spring-security-config.而spring boot security时对spring security框架做了封装,并没有改动spring security。springboot security起步依赖:spring-boot-starter-security
2. Spring Boot Security实例介绍
2.1 配置Spring Boot Security
2.1.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.forezp</groupId> <artifactId>springboot-security</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-security</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <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-web</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- sql--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.1.2 配置Spring Security
新建SecurityConfig配置类,它继承WebSecurityConfigurerAdapter。在SecurityConfig类上加@EnableWebSecurity注解,开启websecurity功能
@EnableWebSecurity @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/css/**", "/index").permitAll() .antMatchers("/user/**").hasRole("USER") .antMatchers("/blogs/**").hasRole("USER") .and() .formLogin().loginPage("/login").failureUrl("/login-error") .and() .exceptionHandling().accessDeniedPage("/401"); http.logout().logoutSuccessUrl("/"); } @Autowired UserDetailsService userDetailsService; // public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); // 在内存中存放用户信息 manager.createUser(User.withUsername("forezp").password("123456").roles("USER").build()); manager.createUser(User.withUsername("admin").password("123456").roles("ADMIN").build()); return manager; } }
上述代码中,
configureGlobal()配置内容如下:
通过AuthenticationManagerBuuilder在内存中创建一个认证用户的信息,该认证用户有两个角色:USER角色(用户名forezp,密码123455),ADMIN角色(用户名为admin密码为123456)
重写的configure()配置如下内容:
a) 以/css/**开头资源和/index资源不需要账户认证,外界请求可以直接访问这些资源
b) 以/user/** 和/blogs/**开头的资源需要验证,并且需要USER角色才可以登录(即用户名forezp,密码123456)
c) 表单登录地址是/login,登陆失败的地址是login-error
d) 异常处理会重定向到"401"界面
e) 注销登录成功,重定向到首页
2.2 Spring Security方法级别上保护
Spring Security从2.0版本开始,提供方法级别的安全支持。Security Config配置类加上@EnableWebSecurity 和@EnableGlobalMethodSecurity开启方法级别保护,代码如下:
@EnableWebSecurity @Configuration @EnableGlobalMethodSecurity(prePostEnabled=true) public SecurityConfig extends WebSecurityConfigurerAdapter{...}
EnbleGlobalMethodSecurity括号中参数如下:
prePostEnabled: Spring Security的Pre和Post注解是否可用,即具体权限接口上@PreAuthorize和@PostAuthorize是否可用;一般来说@PreAuthorize用的多(进入方法前进行权限验证)
secureEnabled: Spring Security的@Secured注解是否可用
jsr250Enabled: Spring Security对JSR-250的注解是否可用
在方法上写权限注解:
比如某个方法有ADMIN权限,则可以在方法上写为@PreAuthorize("hasRole('ADMIN')")或者@PreAuthorize("hasAuthority('ROLE_ADMIN')");
添加多个权限点:@PreAuthorize("hasAnyRole('ADMIN',‘USER')")或者@PreAuthorize("hasAnyAuthority('ROLE_ADMIN','ROLE_USER')")
实例:
两个接口:query() USER权限即可访问,delete() ADMIN权限才可以
@RestController @RequestMapping("/blogs") public class BlogController { @Autowired BlogService blogService; @GetMapping public ModelAndView query(Model model) { List<Blog> list =blogService.getBlogs(); model.addAttribute("blogsList", list); return new ModelAndView("blogs/list", "blogModel", model); } @PreAuthorize("hasAuthority('ROLE_ADMIN')") // @GetMapping(value = "/{id}/deletion") public ModelAndView delete(@PathVariable("id") Long id, Model model) { blogService.deleteBlog(id); model.addAttribute("blogsList", blogService.getBlogs()); return new ModelAndView("blogs/list", "blogModel", model); } }
另外在Security Config中赋登录账号admin以USER和和ADMIN权限
@EnableWebSecurity @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { ... @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); // 在内存中存放用户信息 manager.createUser(User.withUsername("forezp").password("123456").roles("USER").build()); manager.createUser(User.withUsername("admin").password("123456").roles("ADMIN","USER").build()); return manager; } }
程序启动后,当使用forezp作为账号登录后执行delete操作时,返回如下
当使用admin作为账号登陆后执行delete操作时,可以把相应内容删除
2.3 从数据库中读取用户的认证信息
2.1和2.2节中都是采用从内存(代码hardcode)中配置了用户信息,包括用户名、密码以及用户的角色权限信息,但是当用户数量非常多时,这种方式是不可取的。所以需要将从db中读取用户名、密码以及用户角色权限信息。
创建User实体类
注意该实体类需要实现UserDetail(UserDetail接口是springsecurity的一个接口)
UserDetail接口它是实现Spring Security认证信息的核心接口。其中getUsername()为UserDetail接口方法,这个接口不一定返回username,也可以是用户信息(手机号、身份证号等);getAuthorities()返回的是用户设置的权限信息
@Entity public class User implements UserDetails, Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String username; @Column private String password; @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) private List<Role> authorities; public User() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } public void setAuthorities(List<Role> authorities) { this.authorities = authorities; } @Override public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
创建Role实体类
Role类实现GantedAuthority接口,并重写了其getAuthority()
创建Service层
Service层需要实现userDetailService接口(userDetailService是spring security一个接口),该接口是根据用户名获取该用户的所有信息
@Service public class UserService implements UserDetailsService { @Autowired private UserDao userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByUsername(username); } }
修改SecurityConfig
修改SecurityConfig配置类,让Spring Security从db中获取用户的认证信息
@EnableWebSecurity @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/css/**", "/index").permitAll() .antMatchers("/user/**").hasRole("USER") .antMatchers("/blogs/**").hasRole("USER") .and() .formLogin().loginPage("/login").failureUrl("/login-error") .and() .exceptionHandling().accessDeniedPage("/401"); http.logout().logoutSuccessUrl("/"); } @Autowired UserDetailsService userDetailsService; public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); //这里的userDtailService在上面中有具体实现类UserService } }
3. 小结
使用Spring Security首先引入Spring Security相关依赖;
然后写一个配置类,该配置类继承WebSecurityConfigurerAdapter,并在该配置类加上注解@EnableWebSecurity注解开启WebSecurity
然后在配置类中配置AuthenticationManagerBuilder,AuthenticationmangerBuilder配置了用户读取的认证信息方式,可以从内存也可以从db
然后配置HttpSecurity,HttpSecurity配置了请求认证规则,即那些url需要认证以及需要什么权限才能访问
最后如果需要开启方法级别的安全配置需要通过在配置类上加上@EnableGlobalMethodSecurity注解开启
具体代码:https://github.com/forezp/springcloud-book/tree/master/chapter13