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)是人类长久以来的梦想。经历了一次次挫败…...
保姆级使用PyTorch训练与评估自己的MixMIM网络教程
文章目录前言0. 环境搭建&快速开始1. 数据集制作1.1 标签文件制作1.2 数据集划分1.3 数据集信息文件制作2. 修改参数文件3. 训练4. 评估5. 其他教程前言 项目地址:https://github.com/Fafa-DL/Awesome-Backbones 操作教程:https://www.bilibili.co…...
《百万在线 大型游戏服务端开发》前两章概念笔记
第1章 从角色走路说起 游戏网络通信的流程则是服务端先开启监听,等待客户端的连接,然后交互操作,最后断开。 套接字 每个Socket都包含网络连接中一端的信息。每个客户端需要一个Socket结构,服务端则需要N1个Socket结构ÿ…...
3BHE029110R0111 ABB
3BHE029110R0111 ABB变频器控制方式低压通用变频输出电压为380~650V,输出功率为0.75~400kW,工作频率为0~400Hz,它的主电路都采用交—直—交电路。其控制方式经历了以下四代。1U/fC的正弦脉宽调制࿰…...
实现防重复操作(JS与CSS)
实现防重复操作(JS与CSS) 一、前言 日常开发中我们经常会对按钮进行一个防重复点击的校验,这个通常使用节流函数来实现。在规定时间内只允许提交一次,可以有效的避免事件过于频繁的执行和重复提交操作,以及为服务器考…...
怎么合并或注销重复LinkedIn领英帐号?
您可能会发现您拥有多个领英帐户。如果您收到消息,提示您尝试使用的邮箱与另一个帐户已绑定,就表明您可能存在重复的领英帐户。如果您使用许多不同的邮箱地址,也可能会收到这样的提示。 领英精灵温馨提示: 目前,仅支持在 PC 端合并…...
Redis高频面试题汇总(中)
目录 1.什么是redis事务? 2.如何使用 Redis 事务? 3.Redis 事务为什么不支持原子性 4.Redis 事务支持持久性吗 5.Redis事务基于lua脚本的实现 6.Redis集群的主从复制模型是怎样的? 7.Redis集群中,主从复制的数据同步的步骤 …...
【Flutter从入门到入坑之三】Flutter 是如何工作的
【Flutter从入门到入坑之一】Flutter 介绍及安装使用 【Flutter从入门到入坑之二】Dart语言基础概述 【Flutter从入门到入坑之三】Flutter 是如何工作的 本文章主要以界面渲染过程为例,介绍一下 Flutter 是如何工作的。 页面中的各界面元素(Widget&…...
Web Components学习(2)-语法
一、Web Components 对 Vue 的影响 尤雨溪在创建 Vue 的时候大量参考了 Web Components 的语法,下面写个简单示例。 首先写个 Vue 组件 my-span.vue: <!-- my-span.vue --> <template><span>my-span</span> </template>…...
Lesson 9.2 随机森林回归器的参数
文章目录一、弱分类器的结构1. 分枝标准与特征重要性2. 调节树结构来控制过拟合二、弱分类器的数量三、弱分类器训练的数据1. 样本的随机抽样2. 特征的随机抽样3. 随机抽样的模式四、弱分类器的其他参数在开始学习之前,先导入我们需要的库。 import numpy as np im…...
Kubernetes Secret简介
Secret概述 前面文章中学习ConfigMap的时候,我们说ConfigMap这个资源对象是Kubernetes当中非常重要的一个对象,一般情况下ConfigMap是用来存储一些非安全的配置信息,如果涉及到一些安全相关的数据的话用ConfigMap就非常不妥了,因…...
江苏省建设厅网站证件查询/今日油价92汽油价格表
一、题目 给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4…...
网站推广的方案设计怎么写/电话营销外包公司
我们在开发基于.NET的面向对象的程序的时候,抽象类和接口的概念和用法经常会搞不清楚。其实理解它们并不困难。 一、什么是抽象类?抽象类是对某一类型的事物,在编程时,为了描述这类事物,而高度抽象、总结出来的一种基础…...
极速建站系统开发/怎么利用互联网推广
问题 重新拉取Maven依赖时,提示was cached in the local repository, resolution will not be reattempted until the update ... 无法拉取。 解决 原因是本地已经存在相关jar,不会再次拉取。 如果想要重新拉取 删除 *.lastUpdated 删除 _remote.r…...
网站建设的内容要怎么写/怎么在百度推广自己的网站
目 录 第 1 章 概述 ............................................. 错误!未定义书签。 1.1 研究目的和意义 ......................................................................... 错 误!未定义书签。 1.2 国内外发展趋势和现状 ..............…...
wordpress 获取文章列表/徐州seo外包平台
安装centos6.4分区时提示sda必须有一个GPT磁盘标签解决方法具体操作如下:在进入安装界面,分区之前的界面先创建gpt提示:在当前界面操作如下步骤无效按ctrlaltf2的组合键然后进入命令行进行如下操作输入 parted输入 mklabel gpt /dev/sda在提示…...
aws使用wordpress/站长工具seo综合查询推广
背景:对于Android工程 Eclipse里编译好的.so文件放到 libs\armeabi下以后, 这样.so文件就可以打包到apk文件里,在apk装到手机上以后 在libs\armeabi下的.so文件应该就会解压到/data/data/这里是你project的包名(比如com.first.second)/lib下…...