前置知识 1、掌握Spring框架
2、掌握SpringBoot使用
3、掌握JavaWeb技术
SpringSecurity简介 概要 Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。
正如你可能知道的关于安全方面的两个主要区域是“认证 ”和“授权 ”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权 (Authorization) 两个部分,这两点也是 Spring Security 重要核心功能。
用户认证 指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录
用户授权 指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
同款产品对比 SpringSecurity Spring 技术栈的组成部分。
通过提供完整可扩展的认证和授权支持保护你的应用程序
http://spring.io/projects/spring-security
SpringSecurity的特点:
和 Spring 无缝整合。
全面的权限控制。
专门为Web 开发而设计。
重量级。
Shiro Apache 旗下的轻量级权限控制框架。
特点:
轻量级。Shiro主张的理念是把复杂的事情变简单,针对性能又更高要求的互联网应用又更好的表现
通用性
好处:不局限于Web环境,可以脱离Web环境
缺点:在Web环境下一些特定的需求需要手动编写代码制定
Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。
相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。
自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。
因此,一般来说,常见的安全管理技术栈的组合是这样的:
SSM + Shiro
SpringBoot/Spring Cloud + SpringSecurity
SpringSecurity入门程序
默认用户名为:user
密码为:每运行一次 随机生成一个密码
SpringSecurity基本原理 SpringSecurity 本质是一个过滤器链:
从启动是可以获取到过滤器链:
1 2 org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFil ter org.springframework.security.web.context.SecurityContextPersistenceFilter org.springframework.security.web.header.HeaderWriterFilter org.springframework.security.web.csrf.CsrfFilter org.springframework.security.web.authentication.logout.LogoutFilter org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter org.springframework.security.web.savedrequest.RequestCacheAwareFilter org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter org.springframework.security.web.authentication.AnonymousAuthenticationFilter org.springframework.security.web.session.SessionManagementFilter org.springframework.security.web.access.ExceptionTranslationFilter org.springframework.security.web.access.intercept.FilterSecurityInterceptor
代码底层流程:重点看三个过滤器:
FilterSecurityInterceptor: 是一个方法级的权限过滤器, 基本位于过滤链的最底部。
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} else {
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 - super.beforeInvocation(fi) 表示查看之前的 filter 是否通过。 - fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务。 - **ExceptionTranslationFilter:**是个异常过滤器,用来处理在认证授权过程中抛出的异常 - ```java public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { this.doFilter((HttpServletRequest)request, (HttpServletResponse)response, chain); } private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { try { chain.doFilter(request, response); } catch (IOException var7) { IOException ex = var7; throw ex; } catch (Exception var8) { Exception ex = var8; Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex); RuntimeException securityException = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (securityException == null) { securityException = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); } if (securityException == null) { this.rethrow(ex); } if (response.isCommitted()) { throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex); } this.handleSpringSecurityException(request, response, chain, (RuntimeException)securityException); }
UsernamePasswordAuthenticationFilter : 对/login 的 POST 请求做拦截,校验表单中用户名,密码。
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ## UserDetailService接口详解 当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。 如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口定义如下: ```java package org.springframework.security.core.userdetails; public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
返回值:UserDetails
这个类是系统默认的用户“主体 ”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Collection<? extends GrantedAuthority > getAuthorities(); String getPassword () ; String getUsername () ; boolean isAccountNonExpired () ;boolean isAccountNonLocked () ;boolean isCredentialsNonExpired () ;boolean isEnabled () ;
UserDetails实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package org.springframework.security.core.userdetails;import java.io.Serializable;import java.util.Collection;import org.springframework.security.core.GrantedAuthority;public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority > getAuthorities(); String getPassword () ; String getUsername () ; boolean isAccountNonExpired () ; boolean isAccountNonLocked () ; boolean isCredentialsNonExpired () ; boolean isEnabled () ; }
以后我们只需要使用 User 这个实体类即可!
1 2 3 public User (String username, String password, Collection<? extends GrantedAuthority> authorities) { this (username, password, true , true , true , true , authorities); }
方法参数username:
表示用户名。此值是客户端表单传递过来的数据。默认情况下必须叫 username,否则无法接收。
步骤总结:
创建类继承UsernamePassowrdAuthenticationFilter,重写三个方法
创建类实现UserDetailService,编写查询数据过程,返回User对象
这个User对象是安全框架提供对象
PasswordEncoder接口讲解 1 2 3 4 5 6 7 8 9 10 String encode (CharSequence rawPassword) ; boolean matches (CharSequence rawPassword, String encodedPassword) ;default boolean upgradeEncoding (String encodedPassword) {return false ;}
接口实现类:
BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。
BCryptPasswordEncoder 是对bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单向加密。可以通过 strength 控制加密强度,默认10.
方法演示:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test01 () { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder (); String atguigu = bCryptPasswordEncoder.encode("atguigu" ); System.out.println("加密之后数据:\t" +atguigu); boolean result = bCryptPasswordEncoder.matches("atguigu" , atguigu); System.out.println("比较结果:\t" +result); }
SpringBoot对Security的自动配置 https://docs.spring.io/spring- security/site/docs/5.3.4.RELEASE/reference/html5/#servlet-hello
Web权限方案 设置Security密码 以下有三种方式可以配置username和password
通过配置文件 创建application.properties文件
1 2 spring.security.user.name =raehp spring.security.user.password =123456
运行启动类 访问controller 测试
通过配置类 创建securitConfig配置类 标注@Configuration注解 让其继承 WebSecurityConfigurerAdapter
类
并实现其中的configure 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.raehp.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder (); String password = bCryptPasswordEncoder.encode("123" ); auth.inMemoryAuthentication().withUser("raehp" ).password(password).roles("" ); } @Bean public PasswordEncoder password () { return new BCryptPasswordEncoder (); } }
访问控制层,输入账户密码验证通过
自定义编写实现类
第一步、创建配置类,设置使用哪个userDetailService实现类
第二步、编写实现类,返回User对象,User对象有用户名密码和操作权限
创建配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.raehp.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;@Configuration public class SecurityConfig2 extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); } @Bean public PasswordEncoder password () { return new BCryptPasswordEncoder (); } }
编写实现类 创建 service/MyUserService
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.raehp.service;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Service;import java.util.List;@Service("userDetailsService") public class MyUserService implements UserDetailsService { @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { List<GrantedAuthority> authos = AuthorityUtils.commaSeparatedStringToAuthorityList("role" ); return new User ("raehp" ,new BCryptPasswordEncoder ().encode("123" ),authos); } }
运行访问controller、测试通过
连接数据库
引入相关依赖
创建数据库和表
创建对应的实体类
整合mybatis-plus,创建mapper接口 实现BaseMapper接口
在MyUserDetailService中调用mapper中的方法查询函数
数据库配置
一、引入相关依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.0.5</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </dependency >
二、创建数据库和表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 create table users( id bigint primary key auto_increment, username varchar(20) unique not null, password varchar(100) ); -- 密码 atguigu insert into users values(1,'张san','$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na'); -- 密码 atguigu insert into users values(2,'李si','$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na'); create table role( id bigint primary key auto_increment, name varchar(20) ); insert into role values(1,'管理员'); insert into role values(2,'普通用户'); create table role_user( uid bigint, rid bigint ); insert into role_user values(1,1); insert into role_user values(2,2); create table menu( id bigint primary key auto_increment, name varchar(20), url varchar(100), parentid bigint, permission varchar(20) ); insert into menu values(1,'系统管理','',0,'menu:system'); insert into menu values(2,'用户管理','',0,'menu:user'); create table role_menu( mid bigint, rid bigint ); insert into role_menu values(1,1); insert into role_menu values(2,1); insert into role_menu values(2,2);
三、创建实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.raehp.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class Users { public Integer id; public String username; public String password; }
四、创建mapper接口使其继承BaseMapper接口
1 2 3 4 5 6 7 8 9 package com.raehp.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.raehp.pojo.Users;import org.apache.ibatis.annotations.Mapper;@Mapper public interface UserMapper extends BaseMapper <Users> {}
五、在MyUserDetailService中调用mapper中的方法查询函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.raehp.service;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.raehp.mapper.UserMapper;import com.raehp.pojo.Users;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Service;import java.util.List;@Service("userDetailsService") public class MyUserService implements UserDetailsService { @Autowired public UserMapper userMapper; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { QueryWrapper<Users> wrapper = new QueryWrapper <>(); wrapper.eq("username" ,username); Users user = userMapper.selectOne(wrapper); if (user == null ) { throw new UsernameNotFoundException ("用户名不存在" ); } List<GrantedAuthority> authos = AuthorityUtils.commaSeparatedStringToAuthorityList("role" ); return new User (user.getUsername(),new BCryptPasswordEncoder ().encode(user.getPassword()),authos); } }
六、数据库配置
1 2 3 4 spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/xxx?serverTimezone=GMT%2B8 spring.datasource.username =xxx spring.datasource.password =xxx
自定义登录页面
第一步、在配置类实现相关配置
1 2 3 4 5 6 7 8 9 10 11 @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login.html" ) .loginProcessingUrl("/user/login" ) .defaultSuccessUrl("/test/index" ).permitAll() .and().authorizeRequests() .antMatchers("/" ,"/test/hello" ,"/user/login" ).permitAll() .anyRequest().authenticated() .and().csrf().disable(); }
第二步、创建相关的页面 static/login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <form action ="/user/login" method ="post" > 用户名:<input type ="text" name ="username" > <br > 密码:<input type ="password" name ="password" > <br > <input type ="submit" name ="login" > </form > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.atguigu.securitydemo1.controller;import com.atguigu.securitydemo1.entity.Users;import org.springframework.security.access.annotation.Secured;import org.springframework.security.access.prepost.PostAuthorize;import org.springframework.security.access.prepost.PostFilter;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;@RestController @RequestMapping("/test") public class TestController { @GetMapping("hello") public String hello () { return "hello security" ; } @GetMapping("index") public String index () { return "hello index" ; } }
第三步、访问localhost:8081/test/hello
不会被拦截
访问localhost:8081/test/index
会跳转到login.html 页面 输入账户密码 登陆成功后 会访问到 /test/index
基于角色或权限进行访问控制 hasAuthority方法
如果当前的主题具有指定的权限,则返回true,否则返回false(只针对某一个权限)
1 2 3 4 5 6 7 1. 在配置类设置当前访问地址有哪些权限.antMatchers("/test/index" ).hasAuthority("admin" ) 2. 在UserDetatilsService中,给返回的User对象设置权限List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin" );
没有权限 访问后会返回(type = “forbidden” status=”403”)
hasAnyAuthority方法
如果当前的主题有任何提供的角色(给定的作为一个逗号分割的字符串列表)的话,返回true
1 2 3 4 5 6 7 1. 在配置类设置当前访问地址有哪些权限.antMatchers("/test/index" ).hasAnyAuthority("admin,manager" ) 2. 在UserDetatilsService中,给返回的User对象设置权限List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin" );
hasRole方法
如果用户具备给定角色就允许访问,否则出现403,如果当前主体具有指定的角色,则返回true
1 2 3 4 5 6 7 1. 在配置类设置当前访问地址那些角色可以访问.antMatchers("/test/index" ).hasRole("sales" ) 2. 在UserDetatilsService中,给返回的User对象设置权限List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sales" );
注意:这里给User -> role权限时 要加 ROLE_
这是因为源码中自动帮我们添加了
hasAnyRole方法
用户具备任意一个条件即可访问
1 2 3 4 5 6 7 1. 在配置类设置当前访问地址那些角色可以访问.antMatchers("/test/index" ).hasAnyRole("sales,manager" ) 2. 在UserDetatilsService中,给返回的User对象设置权限List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_manager" );
自定义403页面 1 2 3 4 5 6 在自定义配置中配置 http.exceptionHandling().accessDeniedPage("/unauth.html" ); http.formLogin() ....; }
注解使用 @Secured
用户具有某个角色,可以访问方法
在启动类(或配置类)上添加@EnableGlobalMethodSecurity(securedEnabled=true)
在controller上添加@Secured
注解设置角色
userDetailService方法设置当前登录用户角色
第一步、@EnableGlobalMethodSecurity(securedEnabled=true)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.atguigu.securitydemo1;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;@SpringBootApplication @MapperScan("com.atguigu.securitydemo1.mapper") @EnableGlobalMethodSecurity(securedEnabled = true) public class Securitydemo1Application { public static void main (String[] args) { SpringApplication.run(Securitydemo1Application.class, args); } }
第二步、@Secured
1 2 3 4 5 @GetMapping("update") @Secured({"ROLE_sale","ROLE_manager"}) public String update () { return "hello update" ; }
第三步、userDetailService设置登录用户权限
1 2 List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_manager" );
@PreAuthorize
方法执行之前校验
在启动类(或配置了)上添加@EnableGlobalMethodSecurity(prePostEnabled = true)
在controller方法上添加PreAuthorize方法,可以将登录用户的 roles/permissions 参数传到方法中。
第一步、@EnableGlobalMethodSecurity(prePostEnabled = true)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.atguigu.securitydemo1;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;@SpringBootApplication @MapperScan("com.atguigu.securitydemo1.mapper") @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) public class Securitydemo1Application { public static void main (String[] args) { SpringApplication.run(Securitydemo1Application.class, args); } }
第二步、@PreAuthorize
1 2 3 4 5 @GetMapping("update2") @PreAuthorize("hasAnyAuthority('admin')") public String update2 () { return "hello update2" ; }
@PostAuthorize
方法执行后校验
在启动类(或配置了)上添加@EnableGlobalMethodSecurity(prePostEnabled = true)
在controller方法上添加@PostAuthorize
注解,注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.
第一步、@EnableGlobalMethodSecurity(prePostEnabled = true)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.atguigu.securitydemo1;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;@SpringBootApplication @MapperScan("com.atguigu.securitydemo1.mapper") @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) public class Securitydemo1Application { public static void main (String[] args) { SpringApplication.run(Securitydemo1Application.class, args); } }
第二步、@PostAuthorize
1 2 3 4 5 6 @GetMapping("update3") @PostAuthorize("hasAnyAuthority('admins')") public String update3 () { System.out.println("我是方法执行后校验的!" ); return "hello update3" ; }
当我们访问这个请求并且提交后 会出现以下情况:
没有权限访问,但是我们打开控制台发现 方法已经执行完毕
@PostFilter
权限验证之后对数据进行过滤
表达式中的 filterObject 引用的是方法返回值List 中的某一个元素
1 2 3 4 5 6 7 8 9 @GetMapping("getAll") @PostAuthorize("hasAnyAuthority('admin')") @PostFilter("filterObject.username == 'admin1'") public List<Users> getAllUser () { ArrayList<Users> list = new ArrayList <>(); list.add(new Users (11 ,"admin1" ,"6666" )); list.add(new Users (21 ,"admin2" ,"888" )); return list; }
结果如下:
@PreFilter
进入控制器之前对数据进行过滤
1 2 3 4 5 6 7 8 9 10 @GetMapping("getTestPreFilter") @PreAuthorize("hasRole('ROLE_管理员')") @PreFilter(value = "filterObject.id%2==0") @ResponseBody public List<UserInfo> getTestPreFilter (@RequestBody List<UserInfo> list) { list.forEach(t-> { System.out.println(t.getId()+"\t" +t.getUsername()); }); return list; }
用户注销 创建一个登录成功的页面 success.html
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > 登录成功! <a href ="/logout" > 退出</a > </body > </html >
在securityConfig中添加如下配置
1 2 http.logout().logoutUrl("/logout" ).logoutSuccessUrl("/test/hello" ).permitAll();
更新securityConfig 中登录成功后跳转的页面为success.html
1 .defaultSuccessUrl("/success.html" ).permitAll()
类似于浏览器中的session
当我们登陆成功后,可以访问别的页面,但是当我们退出后就不能访问其他页面了
退出之前:
退出之后:
基于数据库设置记住我 第一步、创建数据库表(也可以不创建,设置配置文件时 可以帮我们创建)
1 2 3 4 5 6 7 CREATE TABLE `persistent_logins` ( `username` varchar(64) NOT NULL, `series` varchar(64) NOT NULL, `token` varchar(64) NOT NULL, `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`series`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
第二步、设置数据库配置文件
1 2 3 4 5 spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/xxx?serverTimezone=GMT%2B8 spring.datasource.username =xxx spring.datasource.password =xxx
第三步、在安全配置类中编写数据库配置类
1 2 3 4 5 6 7 8 9 10 11 12 @Autowired private DataSource dataSource;@Bean public PersistentTokenRepository persistentTokenRepository () { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl (); jdbcTokenRepository.setDataSource(dataSource); return jdbcTokenRepository; }
第四步、修改安全配置类
1 2 3 4 5 6 7 8 9 10 11 http.formLogin() .... .and().rememberMe().tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(60 ) .userDetailsService(userDetailsService) -------------------------------------------------------------------------------------- http.rememberMe() .tokenRepository(tokenRepository) .tokenValiditySeconds(60 ) .userDetailsService(usersService);
第五步、页面中设置记住密码复选框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <form action ="/user/login" method ="post" > 用户名:<input type ="text" name ="username" > <br > 密码:<input type ="password" name ="password" > <br > <input type ="checkbox" name ="remember-me" title ="记住密码" > <br > <input type ="submit" name ="login" > </form > </body > </html >
测试 查询数据库
CSRF 跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding ,通常缩写为 CSRF 或者 XSRF , 是一种挟制用户在当前已
登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本 (XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
从 Spring Security 4.0 开始,默认情况下会启用CSRF 保护,以防止CSRF 攻击应用程序,Spring Security CSRF 会针对 PATCH,POST,PUT 和DELETE 方法进行防护。
参考尚硅谷**==SpringSecurity-P19 CSRF==**
SpringSecurity微服务权限方案 什么是微服务 微服务由来 微服务最早由Martin Fowler 与 James Lewis 于 2014 年共同提出,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是 HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
微服务的优势 (1)微服务每个模块就相当于一个单独的项目,代码量明显减少,遇到问题也相对来说比较好解决。
(2)微服务每个模块都可以使用不同的存储方式(比如有的用 redis,有的用 mysql等),数据库也是单个模块对应自己的数据库。
(3)微服务每个模块都可以使用不同的开发技术,开发模式更灵活。
微服务的本质 (1)微服务,关键其实不仅仅是微服务本身,而是系统要提供一套基础的架构,这种架构使得微服务可以独立的部署、运行、升级,不仅如此,这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在功能上则表现为一个统一的整体。这种所谓的“统一的整体”表现出来的是统一风格的界面,统一的权限管理,统一的安全策略,统一的上线过 程,统一的日志和审计方法,统一的调度方式,统一的访问入口等等。
(2)微服务的目的是有效的拆分应用,实现敏捷开发和部署。
微服务认证于授权实现思路