spring security 学习总结

在这里插入图片描述
在这里插入图片描述

原理:内部其实是过滤器链

在这里插入图片描述
在这里插入图片描述
当用户登录发起认证请求时,会通过UsernamePasswordAuthenticationFilter进行用户认证,认证成功之后,SpringSecurity 调用前期配置好的记住我功能,实际是调用了RememberMeService接口,其接口的实现类会将用户的信息生成Token并将它写入 response 的Cookie中,在写入的同时,内部的TokenRepositoryTokenRepository会将这份Token再存入数据库一份。

当用户再次访问服务器资源的时候,首先会经过RememberMeAuthenticationFiler过滤器,在这个过滤器里面会读取当前请求中携带的 Cookie,这里存着上次服务器保存 的Token,然后去数据库中查找是否有相应的 Token,如果有,则再通过UserDetailsService获取用户的信息。

记住我功能的过滤器

从图中可以得知记住我的过滤器在过滤链的中部,注意是在UsernamePasswordAuthenticationFilter之后。
在这里插入图片描述
在 html 中增加记住我复选框checkbox控件,注意其中复选框的name 一定必须为remember-me

<input type="checkbox" name="remember-me" value="true"/>

注册时密码要进行加密后保存
	 BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
	 // 密码加密
     userDTO.setPassword(bCryptPasswordEncoder.encode(userDTO .getPassword()));
     
     xxxmapper.save(userDTO);



编码 - spring boot搭建项目

引入spring-boot-starter-security依賴
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

1,用户 User 类,对应数据库user表的字段

@Data
public class User implements Serializable{

	private static final long serialVersionUID = 8091197205740959431L;
	
	private Long id;
	
	@NotBlank(message = "请输入昵称")
	@Length(max = 30,message = "姓名不能超过30个字符")
	private String nickname;
	
	@NotBlank(message = "请输入用户名")
	@Length(max = 30,message = "用户名不能超过30个字符")
	private String username;
	
	@NotBlank(message = "请输入密码")
	private String password;
	
	@Email(message = "请输入正确的邮箱格式")
	private String email;
	
	private String phoneNumber;
	private String address;
	private String avatar;
	private Boolean isManager;
	private Integer level;
	private Date createTime;
	private Date updateTime;
	private String token;
	private Date lastLoginTime;
}
用户扩展类,后面使用 UserDTO 类操作,必须实现 UserDetails
@Data
public class UserDTO extends User implements UserDetails{
	//这四个暂且都给默认值为true
	private boolean isAccountNonExpired=true;
	private boolean isAccountNonLocked=true;
	private boolean isCredentialsNonExpired=true;
	private boolean isEnabled=true;
	
	//存储权限的集合
	private List<GrantedAuthority> authorities;
}

2,自定义用户信息服务UserDetailsService

@Service
public class CustomUserDetailsService implements UserDetailsService {

	@Autowired
	private UserMapper userMapper;

	/**
	* 根据用户名查找到用户信息,然后返回。Spring Security会处理验证密码是否正确的逻辑
	*/
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		//定义存储权限的列表
		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
		//通过用户名找到用户
		UserDTO userDTO = userMapper.findByUsername(username);
		if (userDTO != null) {
			//通过用户名找到权限
			List<Permission> perms = userMapper.listPermissionByUserId(userDTO.getId());
			for (Permission p : perms) {
				authorities.add(new SimpleGrantedAuthority(p.getName()));
			}
			userDTO.setAuthorities(authorities);
		}
		return userDTO;
	}
}

3,自定义security的配置类SecurityConfig,必须继承WebSecurityConfigurerAdapter(核心配置类)

package com.article.config;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.firewall.DefaultHttpFirewall;
import org.springframework.security.web.firewall.HttpFirewall;
import com.article.service.impl.CustomUserDetailsService;

@Configuration
// @EnableWebSecurity  ※spring boot自动装配,此注解可以不用写
// @EnableGlobalMethodSecurity(prePostEnabled = true)   ※基于方法授权[注解]的时候,需要开启此注解
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
	@Autowired
	private DataSource dataSource;
	@Autowired
	private CustomUserDetailsService userDetailsService;
	
	//用户登录成功后token存活秒数
	@Value("${token.ValiditySeconds}")
	private Integer tokenValiditySeconds;	

	//密码使用 BCryptPasswordEncoder 加密
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    //允许多请求地址多加斜杠  比如 /msg/list   //msg/list
    //因为报这个错误,所以加上了这个方法[org.springframework.security.web.firewall.RequestRejectedException]
    @Bean
    public HttpFirewall httpFirewall() {
        return new DefaultHttpFirewall();
    }
    
	//记住我功能的相关配置
    @Bean
	public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        // 将 DataSource 设置到 PersistentTokenRepository
        tokenRepository.setDataSource(dataSource);
        // 第一次启动的时候自动建表,以后就不能加这行代码了(可以不用这句话,自己手动建表,源码中有语句)
        // persistentTokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }
    

    //安全拦截机制。 ■此授权是基于web授权的方式,要注意写时候的顺序
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/*","/1120/toLogin").permitAll() // 所有 /* 请求和 /1120/toLogin 请求都可以访问
			.antMatchers("/add").hasAuthority("add") // add请求必须拥有add权限
			.antMatchers("/1120/**").authenticated() // 所有/1120/**请求必须通过认证
			.anyRequest().permitAll()  //其他请求可以访问。 ⇒不能放在最前面
			.and()
			.formLogin()  //允许表单登录
			.loginPage("/myLogin").permitAll() //自定义登录的url
			.usernameParameter("username") //自定义登录form的用户名的name
			.passwordParameter("password") //自定义登录form的密码的name
			.loginProcessingUrl("/actionLogin") //自定义登录form的action名字。※点击登录按钮,spring security会按照上面自定义的UserDetailsService去处理登录验证
			.defaultSuccessUrl("/defaultSuccessUrl", true) //登陆成功后跳转的url,可以做 session 和 cookie 等的处一些理
			//.successHandler(customizeSuccessHandler) 使用 successHandler 后,登录成功后不能自动跳转到指定url [自己测试的]
			//.successForwardUrl("/index") //登陆成功后跳转的url,但是地址栏不变。(回退会导致表单重复提交)
			//.defaultSuccessUrl("/index") //此跳转测试无效
			.failureUrl("/loginFail") //登陆失败后跳转的url
			.and()
			.exceptionHandling().accessDeniedPage("/403") //没有权限访问时候跳转的url
			.and()
			.logout()
            .logoutUrl("/1120/myLogout")  //自定义注销路径
            .logoutSuccessUrl("/myLogout") //注销成功后跳转的url。[可以自己再次清楚session或设置cookie之类的一些处理,最后再跳转到登录页面]
			.and()
			.sessionManagement().invalidSessionUrl("/1120/toLogin") //session失效跳转的url
			.and()
			.rememberMe() //记住我
			.tokenRepository(persistentTokenRepository()) // 配置数据库源
			.tokenValiditySeconds(tokenValiditySeconds) //token过期时间
			.userDetailsService(userDetailsService)
			.csrf().disable() //关闭csrf
			;
	}

登录成功后跳转到的 URL

	@GetMapping("/defaultSuccessUrl")
	public String toIndex(HttpServletRequest request,HttpServletResponse response,Model model) {
		
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		
		//获取登录用户信息
		UserDTO user = (UserDTO) authentication.getPrincipal();

		//--------------  下面这块是之前没用"记住我"功能时候,自己写入和更新用户表的token -----------------//
		//清空密码         
		user.setPassword(null);
		//存cookie
		String token = UUID.randomUUID().toString();
		userService.updateTokenById(token, user.getId());
		Cookie cookie = new Cookie("token", token);
		cookie.setMaxAge(24 * 60 * 60);
		response.addCookie(cookie);
		//--------------  上面这块是之前没用"记住我"功能时候,自己写入和更新用户表的token -----------------//
		
		//存session
		request.getSession().setAttribute("user", user);
		
		
		//查询最后一次登录时间
		Date lastLoginTime = user.getLastLoginTime();
		model.addAttribute("lastLoginTime", lastLoginTime);
		//更新最后一次登陆时间为现在的时间
		userService.updateLastLoginTimeById(user.getId());
		
		return "admin/index";
	}

注销成功后跳转到的 URL ★之前没用spring security时候,自己实现的注销,清除session和cookie

@GetMapping("/myLogout")
public String logout(HttpSession session, HttpServletResponse response) {
	session.removeAttribute("user");
	Cookie cookie = new Cookie("token", null);
	cookie.setMaxAge(0);
	cookie.setPath("/**");
	response.addCookie(cookie);
	return "redirect:/1120/toLogin";
}

用户名或密码错误

<!-- th:if="${param.error}" 固定写法-->
<div th:if="${param.error}" th:text="用户名或密码错误"></div>

■基于方法授权的方式【使用注解】

public interface IndexController {

	@GetMapping("/index")
	@PreAuthorize("isAnonymous()")
	public String index(){
		...
	}

	@GetMapping("/save")
	@PreAuthorize("hasAuthority("p1"))
    public String save(){
		...
	}

	@GetMapping("/delete")
	@PreAuthorize("hasAuthority('p_transfer') and hasAuthority('p_read_account')")
	public String delete(){
		...
	}
}

以上配置标明index方法可匿名访问,save方法需要p1权限才可以访问,delete方法需要同时拥有p_transfer和p_read_account 权限才能访问,底层使用WebExpressionVoter投票器,可从AffirmativeBased第23行代码跟踪。




会话

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管 理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取 用户身份。

/**
 * 获取当前登录用户名 
 * @return 
*/ 
private String getUsername(){
	Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
	//判断是否已经认证
	if(!authentication.isAuthenticated()){
		return null;
	}
	Object principal = authentication.getPrincipal();
	String username = null;
	if (principal instanceof org.springframework.security.core.userdetails.UserDetails) {
		username =((org.springframework.security.core.userdetails.UserDetails)principal).getUsername();
	}else {
		username = principal.toString();
	}
	return username;
}
会话超时

可以再sevlet容器中设置Session的超时时间,如下设置Session有效期为3600s;
spring boot 配置文件:

server.servlet.session.timeout=3600s
安全会话cookie

我们可以使用httpOnly和secure标签来保护我们的会话cookie:

  • httpOnly:如果为true,那么浏览器脚本将无法访问cookie

  • secure:如果为true,则cookie将仅通过HTTPS连接发送

spring boot 配置文件:

server.servlet.session.cookie.http‐only=true
server.servlet.session.cookie.secure=true




★ 结合thymeleaf中的一些功能
maven依赖:

<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

导入命名空间:

xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"

案例:

<!--登录注销-->
<div class="right menu">

   <!--如果未登录-->
   <div sec:authorize="!isAuthenticated()">
       <a class="item" th:href="@{/antionLogin}">
           <i class="address card icon"></i> 登录
       </a>
   </div>

   <!--如果已登录-->
   <div sec:authorize="isAuthenticated()">
       <a class="item">
           <i class="address card icon"></i>
          用户名:<span sec:authentication="principal.username"></span>
          角色:<span sec:authentication="principal.authorities"></span>
       </a>
   </div>

   <div sec:authorize="isAuthenticated()">
       <a class="item" th:href="@{/logout}">
           <i class="address card icon"></i> 注销
       </a>
   </div>
</div>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值