SpringCloud(part11) Spring Security详解

1.Spring Security简介

  • 什么是Spring Security

Spring Security采用安全层的概念,使得controller,Service,dao层等以注解的方式来保护应用程序的安全。Spring Security提供了细粒度的权限控制,可以精细到每一个API接口,每一个业务方法,或者每一个操作数据库的DAO层方法。Spring Security提供的是应用程序层的安全解决方法,一个系统的安全还需要考虑传输层和系统层的安全,例如HTTPS协议,服务器部署防火墙等。

  • 为什么选择Spring Security

  对环境的无依赖性,低代码耦合性。将工程部署到一个服务器上,不需要为Spring Security做什么工作。Spring Security提供了十几个安全模块,模块与模块之间耦合性低,而且模块之前可以自由组合来实现特定的需求的安全功能,具有较高的可自定性。

  在安全方面,认证和授权。

  Apache Shiro一半使用在单体架构中,对于微服务架构是无能为力的,Spring Security适合于微服务架构。

2.案例

  • 依赖:

    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.springframework.cloud
        spring-cloud-starter-security
    
    
        org.springframework.boot
        spring-boot-starter-thymeleaf
    
    
  
    org.thymeleaf.extras
    thymeleaf-extras-springsecurity5
    3.0.3.RELEASE

    
        org.springframework.boot
        spring-boot-starter-test
        test
    



    
        
            org.springframework.cloud
            spring-cloud-dependencies
            ${spring-cloud.version}
            pom
            import
        
    
  • 配置:
server:
  port: 8001
spring:
  thymeleaf:
    cache: false
    mode: LEGACYHTML5
    suffix: .html
  • 配置WebSecurityConfigurerAdapter
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //通过这个类在AuthenticationManagerBuilder内存中
    //创建了一个认证用户的信息
    /*
        代码较少但是做了很多安全防护的功能:
        1.应用层的每一个请求都不需要认证
        2.自动生成了一个登陆表单
        3.可以通过name和password来进行认证
        4.用户可以注销
        5.防止了CSRF攻击
        6.Session Fixation保护
        7.安全Header集成...
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().
                withUser("zht").password("123456").roles("admin");
    }

}

注意:如果你使用的是Security5,需要对密码进行加密,不然登录时会抛出运行时异常

@Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    //inMemoryAuthentication 从内存中获取  对内存中的密码进行Bcrypt编码加密
    auth.inMemoryAuthentication().
            passwordEncoder(new BCryptPasswordEncoder()).
            withUser("zht").
            password(
                    new BCryptPasswordEncoder().
                            encode("123456")).
            roles("USER");
    auth.inMemoryAuthentication().
            passwordEncoder(new BCryptPasswordEncoder()).
            withUser("admin").
            password(
                    new BCryptPasswordEncoder().
                            encode("123456")).
            roles("admin");
}
  • 配置HttpSecurity

WebSecurityConfiguraterAdappter已经配置了如何验证用户信息,那么SpringSecurity是否知道所用的用户都需要身份验证,又如何知道要支持基于表单的身份验证呢?工程中的那些资源需要验证?那些资源不需要验证?这时就需要配置HttpSecurity。

在继承 WebSecurityConfigurerAdapter 的类中 重写 configure(HttpSecurity http)方法来配置

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .antMatchers("/css/**","/index").permitAll()
            .antMatchers("/user/**").hasRole("admin")
            .antMatchers("/blogs/**").hasRole("admin")
            .and()
            .formLogin().loginPage("/login").failureForwardUrl("/login-error")
            .and()
            .exceptionHandling().accessDeniedPage("/401");
    http.logout().logoutSuccessUrl("/");
}
/*
    1.以 /css/**开头的资源和 /index 资源不需要验证
    2.以 /user/** 和 、/blogs/** 开头的资源需要验证,而且需要用户的角色是admin
    3.表单登陆的地址是/login,登陆失败的地址是/login-error
    4.异常处理会重定向 /401 界面
    5.注销登录成功,成功重定向到首页
 */

首页:

注意这里有一个版本的不兼容问题,boot2.1.5springsecurity4不匹配




    Good Thymes Virtual Grocery
    


    

Hello Spring Security

这个页面没有受到保护,你可以进行受保护的页面

登录用户:| 用户角色:

登录页面




    Good Thymes Virtual Grocery
    


     

User 角色 zht / 123456

用户名密码错误

返回主页

权限不足,401页面




    Good Thymes Virtual Grocery
    


    

权限不够,拒绝访问

用户:

角色:

未有用户登录

受保护的首页:




    Good Thymes Virtual Grocery
    


    

这个是Security保护的界面

返回首页

管理博客

3.Spring Security方法级别上的保护

Spring Securiyt2.0版本开始,提供了方法级别的安全支持,并提供了JSR-250的支持。

在以上Demo的基础之上:

定义Service接口

public interface BlogService {
    List getBlogs();
    void deleteBlog(Integer id);
}

实现类:

@Service
public class BlogServiceImp implements BlogService {

    private List blogs=new ArrayList();

    public BlogServiceImp() {
      blogs.add(new Blog(1,"Spring Security方法级别上的保护","good!"));
      blogs.add(new Blog(2,"微服务如何独立部署","每个服务单独部署会不会很麻烦!" +
              "如果通过传统的方式很麻烦,我们只需要通过Docker"));
    }

    @Override
    public List getBlogs() {
        return blogs;
    }

    @Override
    public void deleteBlog(Integer id) {
        Iterator iterator=blogs.iterator();
        while(iterator.hasNext()){
            Blog blog= (Blog) iterator.next();
            if(blog.getId()==id){
               iterator.remove();//注意这个地方
            }
        }
    }
}

在控制器方法上加入权限

@RestController
@RequestMapping("/blogs")
public class controller {
    @Autowired
    BlogService blogService;

    @GetMapping
    public ModelAndView list(Model model){
        List blogs=blogService.getBlogs();
        model.addAttribute("blogsList",blogs);
        return new ModelAndView("blog/list","blogModel",model);
    }
    @PreAuthorize("hasAuthority('ROLE_admin')")
    //在方法上开启权限注解
    @GetMapping("/{id}/deletion")
    public ModelAndView delete(Model model,@PathVariable("id") Integer id){
        System.out.println("controller。。。");
        blogService.deleteBlog(id);
        model.addAttribute("blogsList",blogService.getBlogs());
        return new ModelAndView("blog/list","blogModel",model);
    }

}

配置类:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法级别的注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /*
        @EnableGlobalMethodSecurity
        可选参数:prePostEnabled的Pre和Post注解是否可用。即@PreAuthorize和@PostAuthorize
                  secureEnabled: Spring Security的@Secured 注解是否可用
                  jsr250Enabled: Spring Security对JSR-250的注解是否可用
        一般我们只会用到@PreAuthorize注解会在进入方法前进行权限验证
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        
        inMemoryAuthentication //从内存中获取  对内存中的密码进行Bcrypt编码加密
        auth.inMemoryAuthentication().
                passwordEncoder(new BCryptPasswordEncoder()).
                withUser("zht").
                password(
                        new BCryptPasswordEncoder().
                                encode("123456")).
                roles("USER");
        auth.inMemoryAuthentication().
                passwordEncoder(new BCryptPasswordEncoder()).
                withUser("admin").
                password(
                        new BCryptPasswordEncoder().
                                encode("123456")).
                roles("admin","USER");
    }

    @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").failureForwardUrl("/login-error")
                .and()
                .exceptionHandling().accessDeniedPage("/401");
        http.logout().logoutSuccessUrl("/");
        http.formLogin().successForwardUrl("/index");
    }
}

增加页面:




    Good Thymes Virtual Grocery
    


    
博客编号 博客名称 博客内容 操作
删除

4.通过操作数据库 ORM (JPA)访问权限

  • 配置
server:
  port: 8002
spring:
  thymeleaf:
    cache: false
    mode: LEGACYHTML5
    suffix: .html
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring-security
    username: root
    password: 123456
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  • 建立实体类
package com.example.springbootsecurity2.entity;

import java.io.Serializable;

public class Blog implements Serializable {
    private Integer id;
    private String name;
    private String content;

    public Blog(Integer id, String name, String content) {
        this.id = id;
        this.name = name;
        this.content = content;
    }
    public Blog() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
package com.example.springbootsecurity2.entity;

import org.springframework.security.core.GrantedAuthority;

import javax.persistence.*;

@Entity
public class Role implements GrantedAuthority {
    //GrantedAuthority 权限接口
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(nullable = false)
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getAuthority() {
        return name;//返回name作为权限认证的标志
    }

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
package com.example.springbootsecurity2.entity;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;

@Entity
public class User implements UserDetails, Serializable {
    //实现UserDetails接口,该接口是实现Spring Security认证信息的核心接口。
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)//主键自增
    private Integer 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 authorities;



    @Override
    public Collection getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
        //这个方法不用就要返回username。也可以是手机号、邮箱,qq号等
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setAuthorities(List authorities) {
        this.authorities = authorities;
    }
}
  • dao层操作接口
public interface UserDAO extends JpaRepository {
    User findByUsername(String name);
}
  • 修改配置类,从数据库读取
Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法级别的注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /*
        @EnableGlobalMethodSecurity
        可选参数:prePostEnabled的Pre和Post注解是否可用。即@PreAuthorize和@PostAuthorize
                  secureEnabled: Spring Security的@Secured 注解是否可用
                  jsr250Enabled: Spring Security对JSR-250的注解是否可用
        一般我们只会用到@PreAuthorize注解会在进入方法前进行权限验证
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        /*
        inMemoryAuthentication 从内存中获取  对内存中的密码进行Bcrypt编码加密
        auth.inMemoryAuthentication().
                passwordEncoder(new BCryptPasswordEncoder()).
                withUser("zht").
                password(
                        new BCryptPasswordEncoder().
                                encode("123456")).
                roles("USER");
        auth.inMemoryAuthentication().
                passwordEncoder(new BCryptPasswordEncoder()).
                withUser("admin").
                password(
                        new BCryptPasswordEncoder().
                                encode("123456")).
                roles("admin","USER");
                */
        //更换从数据库获取权限信息 Security 5 以后必须对密码进行加密
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @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").failureForwardUrl("/login-error")
                .and()
                .exceptionHandling().accessDeniedPage("/401");
        http.logout().logoutSuccessUrl("/");
        http.formLogin().successForwardUrl("/index");
    }
}
  • 控制器接口
@Controller
public class Testcontroller {
    @RequestMapping("/")
    public String root(){
        return "redirect:index";
    }
    @RequestMapping("/index")
    public String index(){
        return "index";
    }
    @RequestMapping("/login")
    public String login(){
        return "login";
    }
    @RequestMapping("/login-error")
    public String loginError(Model model){
        model.addAttribute("loginError",true);
        return "login";
    }
    @RequestMapping("/401")
    public  String acccessDenied(){
        return "401";
    }

    @RequestMapping("/user/index")
    public String toUserIndex(){
        return "/user/index";
    }
}
@RestController
@RequestMapping("/blogs")
public class controller {
    @Autowired
    BlogService blogService;

    @GetMapping
    public ModelAndView list(Model model){
        List blogs=blogService.getBlogs();
        model.addAttribute("blogsList",blogs);
        return new ModelAndView("blog/list","blogModel",model);
    }
    @PreAuthorize("hasAuthority('ROLE_admin')")
    //在方法上开启权限注解
    @GetMapping("/{id}/deletion")
    public ModelAndView delete(Model model,@PathVariable("id") Integer id){
        System.out.println("controller。。。");
        blogService.deleteBlog(id);
        model.addAttribute("blogsList",blogService.getBlogs());
        return new ModelAndView("blog/list","blogModel",model);
    }

}

还要往数据库中加入记录。

1    $2a$10$6V/KuYL7RyIELEcs4SEmkOuI1I5tF3jo0xOuZIxuHRu/LfxJv6i5q    zht
2    $2a$10$6V/KuYL7RyIELEcs4SEmkOuI1I5tF3jo0xOuZIxuHRu/LfxJv6i5q    admin

1    ROLE_USER
2    ROLE_admin

 

你可能感兴趣的:(SpringCloud)