Spring Security基础入门
基础概念
什么是认证
认证:用户认证就是判断一个用户的身份身份合法的过程,用户去访问系统资源的时候系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户密码登录,二维码登录,手机短信登录,指纹认证等方式。
其中Spring Security使用的是OAuth2.0认证授权协议。
什么是会话
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存到会话中,会话就是系统为了保持用户当前用户的登录状态所提供的机制,常见的有基于Session的方式、基于token等方式
基于Session的认证方式
他的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话中),发给客户端的session_id存放到cookie中,这样用户客户端请求时带上session_id就可以验证服务器是否存在session数据,以此完成用户的合法校验。当用户退出系统或session过期销毁时,客户端的session_id就无效。
方法 | 含义 |
---|---|
HttpSession getSession(Boolean create) | 获取当前HttpSession对象 |
void setAttribute(String name, Object value) | 向session中存放对象 |
void removeAttribute(String name) | 移除session中的对象 |
Object getAttribute(String name) | 从session中获取对象 |
基于Token方式
他的交互流程是,用户认证成功后,服务端生成一个token发送给客户端,客户端可以放到Cookie中或localStorage等存储中,每次请求时带上token,服务端收到的token通过验证后即可确认用户身份。
对比总结
基于session的认证方式有servlet规范定制的,服务端要存储session信息都要占用内存资源,客户端需要支持Cookie;基于token的方式则一般不需要服务器存储token,并且不限制客户端的存储方式。如今移动互联网时代更多类型的用户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更合理。
什么是授权
授权:授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
授权数据模型
授权可简单理解为who对what(which)进行how操作。包括如下:
1、who,即主体,主体一般指用户,也可以是程序,需要访问系统中的资源。
2、what,即资源,如系统菜单,页面,按钮,代码方法,系统商品信息,系统订单等。系统菜单,页面,按钮,代码方法属于系统功能资源,对于web系统每个资源通常对应一个URL,系统商品信息、订单信息等属于实体资源(数据资源),实体资源由资源类型和资源示例组成,比如商品信息为资源类型,商品编号如001为资源的实例
3、how,权限/许可(Permission),规定了用户对资源的操作许可,权限离开资源没有意义,如用户查询权限,用户添加权限,某个代码方法的调用权限,编号为001的用户修改权限等,通过权限可知用户对哪些资源都有哪些许可操作。
主体、资源、权限关系如下图
企业开发中权限表设计如下
资源(资源ID、资源名称、访问地址)
权限(资源ID、权限标识、权限名称、资源ID)
合并为
权限(权限ID、权限标识、权限名称、资源名称、资源访问地址)
数据模型之间的关系如下:
RBAC
基于角色访问控制
RBAC基于角色的访问控制(Role-Based Access Control)是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
基于资源访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权,比如:用户必须具有查询工资权限才能查询员工工资信息等,访问控制流程如下:
Spring Security
简介
Spring Security是基于Spring框架,提供一套web应用安全性解决方案。一般来说,web应用的安全性主要包括用户认证(Authentication)和用户授权(Authorization)两部分。这两点是Spring Security的核心功能。
用户认证:是验证某个用户是否为系统中的合法主体,也就是用户能否访问该系统。
用户授权:是验证某个用户是否有权限执行某个操作。在一个系统中,不同的用户所具有的权限不同的。
spring security在项目中进行应用的时候需要登录才能访问接口,否则401.默认用户名:user,密码:每次启动项目后自动生成在日志中输出
引入Maven依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
快速开始
在controller中编写对应的java代码
@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/add")public String add() {return "hello world";}
}
浏览器访问该接口需要输入用户名以及密码,默认用户名是
user
,密码有控制台日志输出
postman调用接口,需要在
Athorization
中输入用户名以及密码,注意Type选择的是Basic Auth
自定义用户名密码
可以在application.yml文件中添加以下配置即可实现
spring:security:user:name: userpassword: pwd123
除此之外还可以实现UserDetailsService接口来实现自定义用户名密码
配置类
@Configuration
public class SecurityConfiguration {@Beanpublic PasswordEncoder getPasswordEncode() {return new BCryptPasswordEncoder();}
}
使用BCryptPasswordEncoder来对密码进行加密
UserDetailsService实现类
@Service
public class UserDetailServiceImpl implements UserDetailsService {@Value("${application.customize.username:admin}")private String customizeUsername;@Value("${application.customize.password:123}")private String customizePassword;@Value("${application.customize.permission.list:admin,normal}")private String permissionList;@Resourceprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 查询用户名是否在数据存在,如果不存在抛出异常if (!customizeUsername.equals(username)) {throw new UsernameNotFoundException("用户名不存在: {}" + username);}// 如果查询到密码的密码进行解析,或者总结把密码传入构造方法String password = passwordEncoder.encode(customizePassword);// 使用AuthorityUtils构造权限列表return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList(permissionList));}
}
注意:
最忌代码里写死字符串,尽可能的进行配置
配置文件
application:customize:username: adminpassword: 123permission:list: admin,normal
自定义登录页
在resources文件夹下创建一个static文件夹,同时创建
login.html
、error.html
、main.html
页面。在SecurityConfiguration
中继承WebSecurityConfigurerAdapter
并重写configure(HttpSecurity httpSecurity)
方法
配置类代码如下
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {/*** 将Spring Security默认的登录页面路由到自定义的登录页面.* 由于重写了configure方法,所以需要<code>httpSecurity.authorizeRequests()* .anyRequest().authenticated();</code>来进行所有请求的拦截验证。* 同时需要对login.html进行放行不需要验证。同时对/login请求进行* 拦截,登录成功后会跳转到main.html,登录失败跳转到error.html。* 同时需要关闭csrf防护.* @param httpSecurity* @throws Exception*/@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.formLogin().loginProcessingUrl("/login").loginPage("/login.html").successForwardUrl("/toMain").failureForwardUrl("/toError");httpSecurity.authorizeRequests().antMatchers("/error.html").permitAll().antMatchers("/login.html").permitAll().anyRequest().authenticated();httpSecurity.csrf().disable();}@Beanpublic PasswordEncoder getPasswordEncode() {return new BCryptPasswordEncoder();}
}
注意:在一些前后端分离的项目中,这种配置是
无法实现
对应的需求
前后端分离自定义登录页
实现接口
AuthenticationSuccessHandler
并实现接口的中方法,将登录成功后的页面重定向到对应的url
代码示例
public class SecurityAuthenticationSuccessHandler implements AuthenticationSuccessHandler {private String url;public SecurityAuthenticationSuccessHandler(String url) {this.url = url;}/*** 前后端分离进行重定向* @param httpServletRequest* @param httpServletResponse* @param authentication* @throws IOException* @throws ServletException*/@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Authentication authentication) throws IOException, ServletException {httpServletResponse.sendRedirect(url);}
}
登录失败就实现接口
AuthenticationFailureHandler
同时实践其中的方法
代码示例
public class SecurityAuthenticationFailureHandler implements AuthenticationFailureHandler {private String url;public SecurityAuthenticationFailureHandler(String url) {this.url = url;}@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,AuthenticationException e) throws IOException, ServletException {httpServletResponse.sendRedirect(url);}
}
配置类
继承
WebSecurityConfigurerAdapter
重写configure(HttpSecurity httpSecurity)
方法
@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.formLogin().loginProcessingUrl("/login").loginPage("/login.html")
// .successForwardUrl("/toMain")// 登陆成功重定向到https://www.baidu.com.successHandler(new SecurityAuthenticationSuccessHandler("https://www.baidu.com"))
// .failureForwardUrl("/toError");// 登录失败重定向到error.html.failureHandler(new SecurityAuthenticationFailureHandler("/error.html"));httpSecurity.authorizeRequests().antMatchers("/error.html").permitAll().antMatchers("/login.html").permitAll().anyRequest().authenticated();httpSecurity.csrf().disable();}
antMatcher
方法定义
public C antMatchers(String... antPatterns)
参数是不定向参数,每个参数是一个ant表达式,用于匹配url
规则如下:
1、
?
:匹配一个字符2、
*
:匹配0个或多个字符3、
**
:匹配0个或多个目录
在实际项目中经常需要放行所有的静态资源,如下
.antMatchers("/js/**","/css/**").permitAll()
还有一种配置方式是只是
.js
文件都放行
.antMatchers("/**/*.js").permitAll()
regexMatchers
方法定义
public C regexMatchers(String... regexPatterns)
regexMatchers
使用的是正则表达式来匹配对应的资源进行权限校验。除此之外还有通过HttpMethod
来限定匹配的请求类型,如下
httpSecurity.authorizeRequests().regexMatchers(HttpMethod.POST,"/demo").permitAll()// 所有请求都会被认证.anyRequest().authenticated();
mvcMatchers
方法定义
public ExpressionUrlAuthorizationConfigurer<H>.MvcMatchersAuthorizedUrl mvcMatchers(String... patterns)
mvcMatchers
是用来匹配在配置文件中配置的servlet.path
,如下
spring:mvc:servlet:path: /security
httpSecurity.authorizeRequests().mvcMatchers("/hello").servletPath("/security").permitAll()// 所有请求都会被认证.anyRequest().authenticated();
权限控制
权限控制一般需要实现
UserDetailsService
接口中的loadUserByUsername
方法,并在其中设置需要的权限列表,如下
@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 查询用户名是否在数据存在,如果不存在抛出异常if (!customizeUsername.equals(username)) {throw new UsernameNotFoundException("用户名不存在: {}" + username);}// 如果查询到密码的密码进行解析,或者总结把密码传入构造方法String password = passwordEncoder.encode(customizePassword);// 使用AuthorityUtils构造权限列表return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList(permissionList));}
其中
permissionList
就是权限列表,我使用的是@Value
注解进行注入,如下
@Value("${application.customize.permission.list:admin,normal}")
private String permissionList;
配置类
在配置类中继承
WebSecurityConfigurerAdapter
重写configure
方法,可以对其进行权限控制。如admin.html
页面只能admin
进行访问,那么配置如下:
httpSecurity.authorizeRequests().antMatchers("/admin.html").hasAnyAuthority("admin")// 所有请求都会被认证.anyRequest().authenticated();
其中
admin
就是在配置类中的权限列表
角色配置
同上编写
service
实现类并继承UserDetailsService
中的loadUserByUsername
方法,并配置权限列表,但权限列表配置如下
@Value("${application.customize.permission.list:admin,normal,ROLE_hello}")
private String permissionList;
其中的
ROLE_hello
就代表角色配置,注意一定要以ROLE_
开头,否则Spring Security无法识别。
配置类
httpSecurity.authorizeRequests().antMatchers("/role.html").hasAnyRole("hello")// 所有请求都会被认证.anyRequest().authenticated();
这里使用的是
hasAnyRole
进行角色控制
IP地址判断
这里的ip地址指的是
用户浏览器输入的地址
,比如localhost
是本机的地址,也可以是127.0.0.1
,也可以是Windows cmd的ipconfig命令的ip地址
配置类
httpSecurity.authorizeRequests().antMatchers("/main.html").hasIpAddress("127.0.0.1")// 所有请求都会被认证.anyRequest().authenticated();
这里配置的
127.0.0.1
只能在浏览器输入127.0.0.1
才有效,输入localhost
或Windows cmd的ipconfig命令的ip地址
均无法访问。
自定义403处理
编写自定义类
SecurityAccessDeniedHandler
同时实现接口AccessDeniedHandler
,实现handle
方法
Handler配置
@Component
public class SecurityAccessDeniedHandler implements AccessDeniedHandler {/*** 设置响应的状态码,处理403状态码请求,同时* 设置请求头,并返回json字符串* @param httpServletRequest* @param httpServletResponse* @param e* @throws IOException* @throws ServletException*/@Overridepublic void handle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,AccessDeniedException e) throws IOException, ServletException {httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");@Cleanup PrintWriter writer = httpServletResponse.getWriter();writer.write("{\"status\": \"error\", \"message\": \"权限不足请联系管理员\"}");writer.flush();}
}
编写配置类
SecurityConfiguration
继承WebSecurityConfigurerAdapter
重写configure
方法
配置类
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.formLogin().loginProcessingUrl("/login").loginPage("/login.html").successHandler(new SecurityAuthenticationSuccessHandler("/main.html"))// 登录失败重定向到error.html.failureHandler(new SecurityAuthenticationFailureHandler("/error.html"));httpSecurity.authorizeRequests()// 可以写多条对不同的url进行权限认证.antMatchers("/error.html")// 允许访问的权限.permitAll().antMatchers("/login.html").permitAll()// 所有请求都会被认证.anyRequest().authenticated();httpSecurity.csrf().disable();httpSecurity.exceptionHandling().accessDeniedHandler(securityAccessDeniedHandler);
}
httpSecurity.exceptionHandling().accessDeniedHandler(securityAccessDeniedHandler);
设置自定义的AccessDeniedHandler
基于表达式的访问控制
Access方法使用
登录用户权限判断实际上底层实现都是调用access(表达式)
表达式 | 描述 |
---|---|
hasRole(String role) | 如果当前主体具有指定角色,则返回true 。例子:hasRole('admin') ,默认情况下,如果提供的角色不以 开头ROLE_ ,则会添加它。defaultRolePrefix 您可以通过修改on来自定义此行为DefaultWebSecurityExpressionHandler 。 |
hasAnyRole(String… roles) | 如果当前主体具有任何提供的角色(以逗号分隔的字符串列表形式给出),则返回true 。例子:hasAnyRole('admin', 'user') 。 |
hasAuthority(String authority) | 如果当前委托人具有指定的权限,则返回true 。 |
hasAnyAuthority(String… authorities) | 如果当前主体具有任何提供的权限(以逗号分隔的字符串列表形式给出),则返回true 。例子:hasAnyAuthority('read', 'write') 。 |
principal | 允许直接访问代表当前用户的主体对象。 |
authentication | 允许直接访问Authentication 从SecurityContext . |
permitAll | 始终评估为true . |
denyAll | 始终评估为false . |
isAnonymous() | 如果当前主体是匿名用户则返回true 。 |
isRememberMe() | 如果当前主体是记住我的用户,则返回true 。 |
isAuthenticated() | 如果用户不是匿名用户则返回true 。 |
isFullyAuthenticated() | 如果用户不是匿名用户且不是记住我的用户,则返回true 。 |
hasPermission(Object target, Object permission) | 如果用户有权访问给定权限的提供目标,则返回true 。例如,hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) | 如果用户有权访问给定权限的提供目标,则返回true 。例如,hasPermission(1, 'com.example.domain.Message', 'read') 。 |
Access自定义权限控制
编写自定义接口以及自定义方法
public interface SecurityService {Boolean hasPermission(HttpServletRequest httpServletRequest,Authentication authentication);
}
编写对应的实现类,
注意要注册为Spring的一个Bean
@Service
public class SecurityServiceImpl implements SecurityService {@Overridepublic Boolean hasPermission(HttpServletRequest httpServletRequest,Authentication authentication) {// 获取主体Object object = authentication.getPrincipal();if (object instanceof UserDetails) {UserDetails userDetails = (UserDetails) object;// 获取权限Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();// 判断权限是否存在对应的URIreturn authorities.contains(new SimpleGrantedAuthority(httpServletRequest.getRequestURI()));}return false;}
}
通过获取主体以及对应的
URI
来编写对应的权限
通过
httpSecurity.authorizeRequests()..anyRequest().access("");
方法来调用自定义的SecurityServiceImpl
中的自定义hasPermission
方法
配置类
@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.formLogin().loginProcessingUrl("/login").loginPage("/login.html").successHandler(new SecurityAuthenticationSuccessHandler("/main.html"))// 登录失败重定向到error.html.failureHandler(new SecurityAuthenticationFailureHandler("/error.html"));httpSecurity.authorizeRequests()// 可以写多条对不同的url进行权限认证.antMatchers("/error.html")// 允许访问的权限.permitAll().antMatchers("/login.html").permitAll().anyRequest()// 注意是beanName不是类名.access("@securityServiceImpl.hasPermission(request, authentication)");httpSecurity.csrf().disable();httpSecurity.exceptionHandling().accessDeniedHandler(securityAccessDeniedHandler);}
自定义配置类继承
WebSecurityConfigurerAdapter
类重写configure
方法。值得注意的是在登录成功后会跳转到main.html
页面,但由于自定义的Access
中没有main.thml
所以登录成功后会403
。
解决方法
@Service
public class UserDetailServiceImpl implements UserDetailsService {@Value("${application.customize.username:admin}")private String customizeUsername;@Value("${application.customize.password:123}")private String customizePassword;@Value("${application.customize.permission.list:admin,normal,ROLE_hello,/main.html}")private String permissionList;@Resourceprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 查询用户名是否在数据存在,如果不存在抛出异常if (!customizeUsername.equals(username)) {throw new UsernameNotFoundException("用户名不存在: {}" + username);}// 如果查询到密码的密码进行解析,或者总结把密码传入构造方法String password = passwordEncoder.encode(customizePassword);// 使用AuthorityUtils构造权限列表return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList(permissionList));}
}
在
permissionList
加入/main.html
即可
基于注解的访问控制
在
Spring Security
中提供了一些访问控制的注解。这些注解都是默认不可用的,需要通过@EnableGlobalMethodSecurity
进行开启后使用如果设置条件允许,程序正常运行,如果不允许会报500,如下
org.springframework.security.access.AccessDeniedException:不允许访问
这些注解可以写到
Service
接口或方法上也可以写到Controller
或Controller
的方法上。通常情况下都是卸载控制器方法上。控制器接口URL是否允许被访问
@Secured注解
@Secured
是专门用于判断是否具有角色的。能写在方法或类上。参数以ROLE_
开头
启动类添加
@EnableGlobalMethodSecurity(securedEnabled = true)
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityStudyApplication {public static void main(String[] args) {SpringApplication.run(SpringSecurityStudyApplication.class, args);}
}
注意这里的配置类不能配置
successHandler
以及failureHandler
接口
Controller
添加@Secured
@Secured("ROLE_hello")
@PostMapping("/toMain")
public String toMain() {return "redirect:main.html";
}
即角色为
hello
的用户才可以访问该接口,角色由实现接口UserDetailsService
重写loadUserByUsername
方法中即可实现
@PreAuthorize以及@PostAuthorize
@PreAuthorize
和@PostAuthorize
都是方法或类级别的注解
@PreAuthorize
:表示访问方法或类在执行之前判断权限,大多数情况下都是使用这个注解,注解的参数和access()
方法参数取值相同,都是权限表达式
@PostAuthorize
:表示方法或类执行结束后判断权限,该注解很少被使用
启动类添加
@EnableGlobalMethodSecurity(prePostEnabled = true)
,接口Controller
添加@PreAuthorize
@PreAuthorize("hasRole('world')")
@PostMapping("/toMain")
public String toMain() {return "redirect:main.html";
}
注意:同样在配置类中的
configure
方法不能配置successHandler
以及failureHandler
RememberMe
Spring Security
中的RememberMe
,用户只需要在登录时添加remember me
复选框,取值为true
,即可将用户信息存储到数据库中,以后就可以不登录访问了
添加依赖
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
application.yml
配置
spring:datasource:url: jdbc:mysql://localhost:3306/security?userUnicode=true&charseterEncoding=UTF-8&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456
注入
PersistentTokenRepository
@Bean
public PersistentTokenRepository getPersistentTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);// 自动建表,第一次启动需要,第二次启动需要启动// jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;
}
继承
WebSecurityConfigurerAdapter
类,重写configure
方法的配置如下:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.formLogin().loginProcessingUrl("/login").loginPage("/login.html").successForwardUrl("/toMain").failureForwardUrl("/toError");httpSecurity.authorizeRequests()// 可以写多条对不同的url进行权限认证.antMatchers("/error.html")// 允许访问的权限.permitAll().antMatchers("/login.html").permitAll().anyRequest().authenticated();httpSecurity.csrf().disable();httpSecurity.exceptionHandling().accessDeniedHandler(securityAccessDeniedHandler);httpSecurity.rememberMe().userDetailsService(userDetailServiceImpl)// 持久层对象.tokenRepository(persistentTokenRepository);
}
前端
HTML
页面修改如下
<form action="/login" method="post">用户名:<input type="text" name="username"/><br/>密码:<input type="password" name="password"/><br/>记住我<input type="checkbox" name="remember-me" value="true"><br/><input type="submit" value="登录"/>
</form>
注意:这里的
name
一定是remember-me
,value
为true
。登录后在数据库的表里会发现录入有一条数据,该失效时间默认是两周,修改默认失效时间,以及remember-me
的别名如下:
httpSecurity.rememberMe()// 失效时间,单位:秒.tokenValiditySeconds(60)// 设置remember-me的别名.rememberMeParameter("remember-me-alias").userDetailsService(userDetailServiceImpl)// 持久层对象.tokenRepository(persistentTokenRepository);
CSRF
CSRF(Cross-site request forgery)跨站请求伪造,也称为"OneClick Attack"或者Session Riding。通过伪造用户请求访问受信任站点的非法请求
跨域:只要网络协议、IP地址、端口中任何一个不相同就是跨域请求
客户端与服务进行交互时,由于Http协议是无状态的协议,所以引入了Cookie进行记录客户端身份。在Cookie中会存放Session ID用来识别客户端的身份。在跨域情况下,session id可能被第三方恶意劫持,通过这个Session id向服务器发起请求时,服务端会认为该请求是合法的,可能发生很多意想不到的事情。
Spring Security中的CSRF
从Spring Security4开始CSRF防护默认开启,默认会拦截请求。进行CSRF处理。CSRF为了保证不是其他第三方网站访问,要求访问时同时携带参数名为
_csrf
值为token(token在服务器产生的)的内容,如果token和服务器的token匹配成功,则访问成功。
示例
引入thymeleaf依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId><version>3.0.4</version>
</dependency>
<dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
配置类
配置类继承
WebSecurityConfigurerAdapter
重写configure
方法,并放行对应的请求
protected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.formLogin().loginProcessingUrl("/login").loginPage("/showLogin").successForwardUrl("/toMain").failureForwardUrl("/toError");httpSecurity.authorizeRequests()// 可以写多条对不同的url进行权限认证.antMatchers("/error.html")// 允许访问的权限.permitAll().antMatchers("/showLogin").permitAll()// 所有请求都会被认证.anyRequest().authenticated();// httpSecurity.csrf().disable();httpSecurity.exceptionHandling().accessDeniedHandler(securityAccessDeniedHandler);httpSecurity.rememberMe()// 失效时间,单位:秒.tokenValiditySeconds(60).userDetailsService(userDetailServiceImpl)// 持久层对象.tokenRepository(persistentTokenRepository);httpSecurity.logout().logoutSuccessUrl("/logout");}
关闭
Spring Security CSRF
Controller跳转到视图
@RequestMapping("/showLogin")
public String showLogin() {return "login";
}
该视图跳转到
resources/templates/login.html
下的页面
thymeleaf页面
<form action="/login" method="post"><input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>用户名:<input type="text" name="username"/><br/>密码:<input type="password" name="password"/><br/>记住我<input type="checkbox" name="remember-me" value="true"><br/><input type="submit" value="登录"/>
</form>
<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
是表单提交时携带_csrf
相关文章:

Spring Security基础入门
基础概念 什么是认证 认证:用户认证就是判断一个用户的身份身份合法的过程,用户去访问系统资源的时候系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户密码登录&am…...
dnsresolver-limit
文件OperationLimiter.h功能DnsResolver是andnroid中提供DNS能力的小型DNS解析器,limit是其中的一个小模块,支持全局、基于key(UID)的DNS请求限制。DnsResolver是多线程模型,单个DNS请求最多启动3个线程(传统DNS)。在网…...
使用 YoctoProject集成Qt6
By Toradex胡珊逢在嵌入式领域中Qt 作为普遍选择的 UI 方案目前已经发布 Qt6 版本。本文将介绍如何为 Toradex 的计算机模块使用 Yocto Project 将 Qt6 集成到镜像里。首先根据这里的说明,准备好Yocto Project 的编译环境。这里我们选择 Toradex 最新的 Linux BSP V…...

「媒体邀约」如何选择适合的媒体公关,媒体服务供应商
传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 每天胡老师也会接到大量关于媒体方面的询问,胡老师也都一一的很耐心的进行了解答,也都很详细的做了媒体规划和媒体传播方案,但有的朋友还是很犹豫&…...

html2canvas和jspdf导出pdf,每个页面模块占一页,在pdf中垂直居中显示
需求:html页面转换pdf,页面有多个模块,页面中有文本、echarts、表格等模块,一个模块占一页,因为模块高度不够,所以需要垂直居中 通过html2canvas和jspdf实现,html2canvas用于将页面元素生成canv…...
数学小课堂:集合论的公理化过程(用构建公理化体系的思路来构建自然数)
文章目录 引言I 数的理论1.1 构建自然数1.2 定义整数/有理数/实数/虚数/复数II 自然数和集合的关系1.3 集合1.2 构建自然数III 线性规划问题(线性代数+最优化)3.1 题目3.2 答案引言 数学是一个公理化的体系,是数学对其它知识体系有启发的地方。 数学的思维方式: 不轻易相信…...

3.10多线程
一.常见锁策略1.悲观锁 vs乐观锁体现在处理锁冲突的态度①悲观锁:预期锁冲突的概率高所以做的工作更多,付出的成本更多,更低效②乐观锁:预期锁冲突的概率低所以做的工作少,付出的成本更低,更搞笑2.读写锁 vs 普通的互斥锁①普通的互斥锁,只有两个操作 加锁和解锁只有两个线程针…...

缓存双写一致性之更新策略探讨
问题由来 数据redis和MySQL都要有一份,如何保证两边的一致性。 如果redis中有数据:需要和数据库中的值相同如果redis中没有数据:数据库中的值是最新值,且准备会写redis 缓存操作分类 自读缓存读写缓存: ࿰…...
scala高级函数快速掌握
scala高级函数一.函数至简原则二.匿名的简化原则三.高阶函数四.柯里化和闭包五.递归六.抽象控制七.惰性加载🔥函数对于scala(函数式编程语言)来说非常重要,大家一定要学明白,加油!!!…...

手写模拟SpringMvc源码
MVC框架MVC是一种设计模式(设计模式就是日常开发中编写代码的一种好的方法和经验的总结)。模型(model)-视图(view)-控制器(controller),三层架构的设计模式。用于实现前端…...

五分钟了解JumpServer V2.* 与 v3 的区别
一、升级注意项 1、梳理数据。JumpServer V3 去除了系统用户功能,将资产与资产直接绑定。当一个资产名下有多个同名账号,例如两个root用户时,升级后会自动合并最后一个root,不会同步其他root用户。升级前需保证每一个资产只拥有一…...

用友开发者中心应用构建实践指引!
基于 iuap 技术底座,用友开发者中心致力于为企业和开发者提供一站式技术服务,让人人都能轻松构建企业级应用。 本文以人力资源领域常用的应聘人员信息登记与分析功能为例,详细介绍如何在用友开发者中心使用 YonBuilder 进行应用构建。 功能…...

snap使用interface:content的基础例子
snap做包还在学习阶段,官网文档可查看:The content interface | Snapcraft documentation该例子由publiser和consumer两部分组成,一个提供一个只读的数据区,一个来进行读取其中的信息,这样就完成了content的交互。publ…...
蓝桥杯刷题第七天
第一题:三角回文数问题描述对于正整数 n, 如果存在正整数 k 使得2n123⋯k2k(k1), 则 n 称为三角数。例如, 66066 是一个三角数, 因为 66066123⋯363。如果一个整数从左到右读出所有数位上的数字, 与从右到左读出所有数位 上的数字是一样的, 则称这个数为回文数。例如…...

FinOps首次超越安全成为企业头等大事|云计算趋势报告
随着云计算在过去十年中的广泛应用,云计算用户所面临的一个持续不变的趋势是:安全一直是用户面临的首要挑战。然而,这种情况正在发生转变。 知名IT软件企业 Flexera 对云计算决策者进行年度调研已经持续12年,而今年安全问题首次…...

【深度强化学习】(3) Policy Gradients 模型解析,附Pytorch完整代码
大家好,今天和各位分享一下基于策略的深度强化学习方法,策略梯度法是对策略进行建模,然后通过梯度上升更新策略网络的参数。我们使用了 OpenAI 的 gym 库,基于策略梯度法完成了一个小游戏。完整代码可以从我的 GitHub 中获得&…...

Windows基于Nginx搭建RTMP流媒体服务器(附带所有组件下载地址及验证方法)
RTMP服务时常用于直播时提供拉流推流传输数据的一种服务。前段时间由于朋友想搭建一套直播时提供稳定数据传输的服务器,所以就研究了一下如何搭建及使用。 1、下载nginx 首先我们要知道一般nginx不能直接配置rtmp服务,在Windows系统上需要特殊nginx版本…...

交流电机驱动器中的隔离电压感应
汽车和工业终端设备,如电机驱动器、串式逆变器和机载充电器,在高电压下运行,不能安全地与人直接互动。隔离电压测量通过保护人类免受高压电路执行一个功能的影响,有助于优化操作和确保使用的安全性。 设计用于高性能,隔…...
爬取知乎问题答案
参考博客:基于Python知乎回答爬虫 jieba关键字统计可视化_知乎爬虫搜索关键词_菠萝柚王子的博客-CSDN博客 1、安装依赖包 import numpy import requests import certifi from PIL import Image from lxml import etree import jieba from wordcloud import WordClo…...
通用智能理论
将智能定义为解决矛盾的能力,用解决矛盾的概率提升来评估智能程度,以此为基础推导智能原理,建立一种新的通用智能理论。 1 前言 通用人工智能(Artificial General Intelligence)是人类长久以来的梦想。经历了一次次挫败…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...
el-amap-bezier-curve运用及线弧度设置
文章目录 简介示例线弧度属性主要弧度相关属性其他相关样式属性完整示例链接简介 el-amap-bezier-curve 是 Vue-Amap 组件库中的一个组件,用于在 高德地图 上绘制贝塞尔曲线。 基本用法属性path定义曲线的路径,可以是多个弧线段的组合。stroke-weight线条的宽度。stroke…...

篇章一 论坛系统——前置知识
目录 1.软件开发 1.1 软件的生命周期 1.2 面向对象 1.3 CS、BS架构 1.CS架构编辑 2.BS架构 1.4 软件需求 1.需求分类 2.需求获取 1.5 需求分析 1. 工作内容 1.6 面向对象分析 1.OOA的任务 2.统一建模语言UML 3. 用例模型 3.1 用例图的元素 3.2 建立用例模型 …...
【学习记录】使用 Kali Linux 与 Hashcat 进行 WiFi 安全分析:合法的安全测试指南
文章目录 📌 前言🧰 一、前期准备✅ 安装 Kali Linux✅ 获取支持监听模式的无线网卡 🛠 二、使用 Kali Linux 进行 WiFi 安全测试步骤 1:插入无线网卡并确认识别步骤 2:开启监听模式步骤 3:扫描附近的 WiFi…...
qt 双缓冲案例对比
双缓冲 1.双缓冲原理 单缓冲:在paintEvent中直接绘制到屏幕,绘制过程被用户看到 双缓冲:先在redrawBuffer绘制到缓冲区,然后一次性显示完整结果 代码结构 单缓冲:所有绘制逻辑在paintEvent中 双缓冲:绘制…...