Spring Security采用安全层的概念,使得controller,Service,dao层等以注解的方式来保护应用程序的安全。Spring Security提供了细粒度的权限控制,可以精细到每一个API接口,每一个业务方法,或者每一个操作数据库的DAO层方法。Spring Security提供的是应用程序层的安全解决方法,一个系统的安全还需要考虑传输层和系统层的安全,例如HTTPS协议,服务器部署防火墙等。
对环境的无依赖性,低代码耦合性。将工程部署到一个服务器上,不需要为Spring Security做什么工作。Spring Security提供了十几个安全模块,模块与模块之间耦合性低,而且模块之前可以自由组合来实现特定的需求的安全功能,具有较高的可自定性。
在安全方面,认证和授权。
Apache Shiro一半使用在单体架构中,对于微服务架构是无能为力的,Spring Security适合于微服务架构。
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
@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");
}
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.注销登录成功,成功重定向到首页
*/
首页:
Good Thymes Virtual Grocery
Hello Spring Security
这个页面没有受到保护,你可以进行受保护的页面
登录用户:|
用户角色:
登录页面
Good Thymes Virtual Grocery
User 角色 zht / 123456
用户名密码错误
返回主页
权限不足,401页面
Good Thymes Virtual Grocery
权限不够,拒绝访问
用户:
角色:
未有用户登录
受保护的首页:
Good Thymes Virtual Grocery
这个是Security保护的界面
Spring Securiyt2.0版本开始,提供了方法级别的安全支持,并提供了JSR-250的支持。
在以上Demo的基础之上:
定义Service接口
public interface BlogService { ListgetBlogs(); void deleteBlog(Integer id); }
实现类:
@Service public class BlogServiceImp implements BlogService { private Listblogs=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){ Listblogs=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
博客编号 | 博客名称 | 博客内容 | 操作 |
删除 |
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 Listauthorities; @Override public Collection extends GrantedAuthority> 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; } }
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){ Listblogs=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