赞
踩
Spring Security采用安全层的概念,使得controller,Service,dao层等以注解的方式来保护应用程序的安全。Spring Security提供了细粒度的权限控制,可以精细到每一个API接口,每一个业务方法,或者每一个操作数据库的DAO层方法。Spring Security提供的是应用程序层的安全解决方法,一个系统的安全还需要考虑传输层和系统层的安全,例如HTTPS协议,服务器部署防火墙等。
对环境的无依赖性,低代码耦合性。将工程部署到一个服务器上,不需要为Spring Security做什么工作。Spring Security提供了十几个安全模块,模块与模块之间耦合性低,而且模块之前可以自由组合来实现特定的需求的安全功能,具有较高的可自定性。
在安全方面,认证和授权。
Apache Shiro一半使用在单体架构中,对于微服务架构是无能为力的,Spring Security适合于微服务架构。
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-security</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-thymeleaf</artifactId>
- </dependency>
- <!--thymeleaf 对 Spring Security的支持-->
- <dependency>
- <groupId>org.thymeleaf.extras</groupId>
- <artifactId>thymeleaf-extras-springsecurity5</artifactId>
- <version>3.0.3.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
-
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>${spring-cloud.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>

- 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.注销登录成功,成功重定向到首页 */
首页:
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:th="http://www.thymeleaf.org"
- xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
- <head>
- <title>Good Thymes Virtual Grocery</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- </head>
- <body>
- <h1>Hello Spring Security</h1>
- <p>这个页面没有受到保护,你可以进行受保护的页面</p>
- <div th:fragment="logout" sec:authorize="isAuthenticated()">
- 登录用户:<span sec:authentication="name"></span>|
- 用户角色:<span sec:authentication="principal.authorities"></span>
- <div>
- <form action="#" th:action="@{/logout}" method="post">
- <input type="submit" value="注销"/>
- </form>
- </div>
- </div>
- <ul><li>
- <a href="/user/index" th:href="@{/user/index}">去/user/index被保护的页面</a>
- </li></ul>
- </body>
- </html>

登录页面
- <!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
- <head>
- <title>Good Thymes Virtual Grocery</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- </head>
- <body>
- <p>User 角色 zht / 123456</p>
- <p th:if="${loginError}" style="color: red">用户名密码错误</p>
- <form th:action="@{/login}" method="post">
- <label for="username">用户名</label>
- <input id="username" name="username" type="text"/>
- <label for="password">密码</label>
- <input id="password" name="password" type="password"/>
- <input type="submit" value="登录"/>
- </form>
- <a th:href="@{/index}">返回主页</a>
- </body>
- </html>

权限不足,401页面
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
- xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
- <head>
- <title>Good Thymes Virtual Grocery</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- </head>
- <body>
- <h1>权限不够,拒绝访问</h1>
- <div sec:authorize="isAuthenticated()">
- <p>用户:<span sec:authentication="name"></span></p>
- <p>角色:<span sec:authentication="principal.authorities"></span></p>
- </div>
- <div sec:authorize="isAuthenticated()">
- <p>未有用户登录</p>
- </div>
- </body>
- </html>

受保护的首页:
- <!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
- <head>
- <title>Good Thymes Virtual Grocery</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- </head>
- <body>
- <div th:substituteby="index::logout"></div>
- <h1>这个是Security保护的界面</h1>
- <p><a href="/index" th:href="@{/index}">返回首页</a></p>
- <p><a href="/blogs" th:href="@{/blogs}">管理博客</a></p>
- </body>
- </html>
Spring Securiyt2.0版本开始,提供了方法级别的安全支持,并提供了JSR-250的支持。
在以上Demo的基础之上:
定义Service接口
public interface BlogService { List<Blog> getBlogs(); void deleteBlog(Integer id); }
实现类:
@Service public class BlogServiceImp implements BlogService { private List<Blog> blogs=new ArrayList<Blog>(); public BlogServiceImp() { blogs.add(new Blog(1,"Spring Security方法级别上的保护","good!")); blogs.add(new Blog(2,"微服务如何独立部署","每个服务单独部署会不会很麻烦!" + "如果通过传统的方式很麻烦,我们只需要通过Docker")); } @Override public List<Blog> 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<Blog> 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"); } }
增加页面:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> <head> <title>Good Thymes Virtual Grocery</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> <body> <table> <tr> <td>博客编号</td> <td>博客名称</td> <td>博客内容</td> <td>操作</td> </tr> <tr th:each="blog:${blogsList}"> <td th:text="${blog.id}"></td> <td th:text="${blog.name}"></td> <td th:text="${blog.content}"></td> <td><a th:href="@{'/blogs/'+${blog.id}+'/deletion'}">删除</a></td> </tr> </table> </body> </html>
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<Role> authorities; @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<Role> authorities) { this.authorities = authorities; } }
public interface UserDAO extends JpaRepository<User,Integer> { 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<Blog> 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。