《Java后端安全开发Spring Security开发REST服务》4章-1——spring security

0. 概述

  1. AbstractChannelSecurityConfig中http.formLogin()启用form表单登录
  2. 使用默认的DaoAuthenticationProvider实现用户名密码登录
  3. 自己实现UserService
    UserService加了@Component注解,SpringSecurity会自动扫描加载他

1. 起步

1.1 扩展WebSecurityConfigurerAdapter类

配置路由、密码加密类等

package com.wxm.spring.security.browser;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import net.bytebuddy.implementation.bind.annotation.Super;

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private MyUserDetailsService userDetailsService;

	//密码加密类
	//MyUserDetailsService 中会通过@Autowired引入
	@Bean
	public PasswordEncoder encoder() {
	    return new BCryptPasswordEncoder(11);
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		
		http.formLogin()	//表单登录
			.loginPage("/imooc-signIn.html")	//指定登录页面,替代spring security默认页面
			.loginProcessingUrl("/authentication/form")		//对应form表单中的action,UsernamePasswordAuthenticationFilter将处理此URL对应的请求
			//http.httpBasic()	//http登录对话框
			.and()
			.authorizeRequests()
			.antMatchers("/imooc-signIn.html").permitAll()	//别忘了,不然会循环跳转
			.anyRequest()
			.authenticated()
			.and()
			.csrf().disable();	//关闭CSRF验证
	}
	
	/*
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //inMemoryAuthentication 从内存中获取  
		auth.inMemoryAuthentication()
			.passwordEncoder(new BCryptPasswordEncoder())
				.withUser("user1")
				.password(new BCryptPasswordEncoder()
						.encode("123456"))
			.roles("USER");
	}
	*/

	@Override
	protected void configure(AuthenticationManagerBuilder auth)
	  throws Exception {
	    auth.authenticationProvider(authenticationProvider());
	}
	 
	@Bean
	public DaoAuthenticationProvider authenticationProvider() {
	    DaoAuthenticationProvider authProvider
	      = new DaoAuthenticationProvider();
	    authProvider.setUserDetailsService(userDetailsService);
	    authProvider.setPasswordEncoder(encoder());
	    return authProvider;
	}
}

1.2. 实现UserDetailsService 接口

loadUserByUsername根据用户名查找并返回用户信息

@Component
public class MyUserDetailsService implements UserDetailsService {
	@Autowired
	PasswordEncoder passwordEncoder;
	//@Autowired
	//private Mapper
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		//根据用户名查找用户信息
		User user;
		
		//user = new User(username,new BCryptPasswordEncoder().encode("1"),AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
		logger.info("根据用户名查找用户信息 "+username);
		
		/*
		user = new User(username, new BCryptPasswordEncoder().encode("1"),
				true, true, true, true, 
				AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
		*/
		String password = passwordEncoder.encode("1");
		logger.info("数据库密码是: "+password);
		user = new User(username, password,
				true, true, true, true, 
				AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
		logger.info("根据查找到的用户信息判断该用户是否被冻结");
		return user;
	}

}

1.3. 创建/main/java/resources/resources/imooc-signIn.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>imooc-signIn.html</h2>
	<form action="/authentication/form" method="post">
		<table>
			<tr>
				<td>用户名:</td>
				<td><input type="text" name="username"></td>
			</tr>
			<tr>
				<td>密码:</td>
				<td><input type="password" name="password"></td>
			</tr>
			<tr>
				<td colspan="2"><button type="submit">登录</button></td>
			</tr>
		</table>
	</form>
</body>
</html>

2. 配置项管理

在这里插入图片描述

涉及内容:

2.1 wxm-spring-security-demo

2.1.1 application.properties

/main/java/resources/application.properties:

#自定义登录页,替换默认登录页,由SecurityProperties读取和管理
imooc.security.browser.loginPage = /demo-signIn.html

2.1.2 demo-signIn.html

/main/java/resources/resources/demo-signIn.html

2.2 wxm-spring-security-browser

2.2.1 BrowserSecurityConfig

  1. 配置路由
  2. 设置密码加密类
package com.wxm.spring.security.browser;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.wxm.spring.security.core.properties.SecurityProperties;

import net.bytebuddy.implementation.bind.annotation.Super;

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private MyUserDetailsService userDetailsService;

	@Autowired
	private SecurityProperties securityProperties;
	
	//密码加密类
	//MyUserDetailsService 中会通过@Autowired引入
	@Bean
	public PasswordEncoder encoder() {
	    return new BCryptPasswordEncoder(11);
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		
		/*
		http.formLogin()	//表单登录
			.loginPage("/imooc-signIn.html")	//指定登录页面,替代spring security默认页面
			.loginProcessingUrl("/authentication/form")		//对应form表单中的action,UsernamePasswordAuthenticationFilter将处理此URL对应的请求
			//http.httpBasic()	//http登录对话框
			.and()
			.authorizeRequests()
			.antMatchers("/imooc-signIn.html").permitAll()	//别忘了,不然会循环跳转
			//.antMatchers("/authentication/form").permitAll()	//别忘了,不然会循环跳转
			.anyRequest()
			.authenticated()
			.and()
			.csrf().disable();
		*/
		/*
		http.formLogin()	//表单登录
		.loginPage("/authentication/require")	//跳转到BrowserSecurityController对应路径
		.loginProcessingUrl("/authentication/form")		//对应form表单中的action,UsernamePasswordAuthenticationFilter将处理此URL对应的请求
		//http.httpBasic()	//http登录对话框
		.and()
		.authorizeRequests()
		.antMatchers("/authentication/require").permitAll()	//别忘了,不然会循环跳转
		.anyRequest()
		.authenticated()
		.and()
		.csrf().disable();
		*/
		
		http.formLogin()	//表单登录
		.loginPage("/authentication/require")	//跳转到BrowserSecurityController对应路径
		.loginProcessingUrl("/authentication/form")		//对应form表单中的action,UsernamePasswordAuthenticationFilter将处理此URL对应的请求
		//http.httpBasic()	//http登录对话框
		.and()
		.authorizeRequests()
		.antMatchers("/authentication/require"
				,securityProperties.getBrowser().getLoginPage()).permitAll()	//别忘了,不然会循环跳转
		//此处securityProperties.getBrowser().getLoginPage()读出的就是wxm-spring-security-demo//main/java/resources/application.properties中的"imooc.security.browser.loginPage = /demo-signIn.html"
		//.antMatchers("/authentication/form").permitAll()	//别忘了,不然会循环跳转
		.anyRequest()
		.authenticated()
		.and()
		.csrf().disable();
	}
	
	/*
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //inMemoryAuthentication 从内存中获取  
		auth.inMemoryAuthentication()
			.passwordEncoder(new BCryptPasswordEncoder())
				.withUser("user1")
				.password(new BCryptPasswordEncoder()
						.encode("123456"))
			.roles("USER");
	}
	*/

	@Override
	protected void configure(AuthenticationManagerBuilder auth)
	  throws Exception {
	    auth.authenticationProvider(authenticationProvider());
	}
	 
	@Bean
	public DaoAuthenticationProvider authenticationProvider() {
	    DaoAuthenticationProvider authProvider
	      = new DaoAuthenticationProvider();
	    authProvider.setUserDetailsService(userDetailsService);
	    authProvider.setPasswordEncoder(encoder());
	    return authProvider;
	}

}

2.2.2 BrowserSecurityController

自定义用户登录校验跳转逻辑

package com.wxm.spring.security.browser;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.wxm.spring.security.browser.support.SimpleResponse;
import com.wxm.spring.security.core.properties.SecurityProperties;

import org.springframework.security.web.RedirectStrategy;

@RestController
public class BrowserSecurityController {
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	//用于获取引发跳转的请求,请求缓存器
	private RequestCache requestCache = new HttpSessionRequestCache();
	//用于请求跳转
	private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
	
	@Autowired
	private SecurityProperties SecurityProperties;
	
	/**
	 * 当需要身份认证时,跳转到这里
	 * @param request
	 * @param response
	 * @return
	 * @throws IOException 
	 */
	@RequestMapping("/authentication/require")
	@ResponseStatus(code = HttpStatus.UNAUTHORIZED)	//401
	public SimpleResponse requireAuthentication(HttpServletRequest request,HttpServletResponse response) throws IOException {
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		
		if(savedRequest!=null) {
			String targetUrl = savedRequest.getRedirectUrl();
			logger.info("引发跳转的请求是: "+targetUrl);
			//请求路径以.html代表来源是浏览器,否则来源是客户端调用接口,例如:http://localhost/user/1
			if(StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
				//跳转到application.properties中配置的登录页路径,会直接跳转走,后续代码不会继续执行
				redirectStrategy.sendRedirect(request, response, SecurityProperties.getBrowser().getLoginPage());
			}
		}
		
		//不是来源于.html的浏览器请求,那么就是来源于客户端,返回一个字符串提示
		return new SimpleResponse("访问的服务需要身份认证,请引导用户到登录页");
	}
}

2.2.3 MyUserDetailsService

loadUserByUsername根据用户名查找用户信息返回User(implements UserDetails)

package com.wxm.spring.security.browser;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class MyUserDetailsService implements UserDetailsService {
	@Autowired
	PasswordEncoder passwordEncoder;
	//@Autowired
	//private Mapper
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		//根据用户名查找用户信息
		User user;
		
		//user = new User(username,new BCryptPasswordEncoder().encode("1"),AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
		logger.info("根据用户名查找用户信息 "+username);
		
		/*
		user = new User(username, new BCryptPasswordEncoder().encode("1"),
				true, true, true, true, 
				AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
		*/
		String password = passwordEncoder.encode("1");
		logger.info("数据库密码是: "+password);
		user = new User(username, password,
				true, true, true, true, 
				AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
		logger.info("根据查找到的用户信息判断该用户是否被冻结");
		return user;
	}

}

2.2.4 SimpleResponse

一个简单的封装类,用于BrowserSecurityController的请求应答信息封

package com.wxm.spring.security.browser.support;

public class SimpleResponse {

	private Object contentObject;

	public Object getContentObject() {
		return contentObject;
	}

	public void setContentObject(Object contentObject) {
		this.contentObject = contentObject;
	}

	public SimpleResponse(Object contentObject) {
		super();
		this.contentObject = contentObject;
	}
}

2.2.5 imooc-signIn.html

默认登录页面
/main/java/resources/resources/imooc-signIn.html

2.3 wxm-spring-security-core

2.3.1 SecurityCoreConfig

加载SecurityProperties,读取各个子模块中application.properties中的配置项

package com.wxm.spring.security.core;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import com.wxm.spring.security.core.properties.SecurityProperties;

@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
//使配置读取器SecurityProperties生效
public class SecurityCoreConfig {
}

2.3.1 SecurityProperties

顶级类,管理所有配置项,调用BrowserProperties等,读取各个子模块中application.properties中的配置项

package com.wxm.spring.security.core.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

//prefix对应application.properties中的变量前缀:imooc.security.browser.loginPage = /demo-signIn.html
@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {

	//变量名browser一定要与application.properties中的项目名对应上:imooc.security.browser.loginPage = /demo-signIn.html,不然无法自动读出来
	private BrowserProperties browser = new BrowserProperties();

	public BrowserProperties getBrowser() {
		return browser;
	}

	public void setBrowser(BrowserProperties browser) {
		this.browser = browser;
	}
}

2.3.1 BrowserProperties

读取wxm-spring-security-browser的application.properties中的配置项

package com.wxm.spring.security.core.properties;

/**
 * @author wxm
 * 读取wxm-spring-security-demo/main/java/resources/application.properties中的imooc.security.browser.loginPage = /demo-signIn.html
 * 注意私有成员变量loginPage与application.properties中的配置项名称对应关系
 */
public class BrowserProperties {

	private String loginPage = "/imooc-signIn.html";	
	//默认跳转到登录页:/imooc-signIn.html
	//如果application.properties中做了配置,则跳转到定制的登录页

	public String getLoginPage() {
		return loginPage;
	}

	public void setLoginPage(String loginPage) {
		this.loginPage = loginPage;
	}
	
}

在这里插入图片描述

3.自定义登录成功和失败处理

在这里插入图片描述
在这里插入图片描述
AuthenticationSuccessHandler接口,返回Authentication,包含用户认证信息,根据登录方式不同(用户名密码、微信、qq等),包含的信息也不一样
在这里插入图片描述
AuthenticationFailureHandler接口,

4认证处理流程说明

在这里插入图片描述

  1. UsenamePasswrdAuthenticationFilter
    获取并封装用户名、密码等登录信息,生成AuthenticationToken
  2. AuthenticationManager
    管理AuthenticatioinProvider,以AuthenticationToken为参数调用各个AuthenticatioinProvider
  3. AuthenticatioinProvider
    实现认证逻辑:用户名密码、微信、qq等,每个AuthenticatioinProvider根据传入的AuthenticationToken验证是否是属于自己负责的认证逻辑,如果是则进行认证校验;校验过程为:通过UserDetailService获取UserDetail,验证有效性,验证通过则创建并返回Authentication,并以Authentication为参数,调用自定义的AuthenticationSuccessHandler接口实现类,不通过则生成对应异常,并以该异常为参数,调用自定义的AuthenticationFailureHandler接口实现类
  4. UserDetailService
  5. UserDetail
  6. Authentication

5认证处理结果如何在多个请求之间共享

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

AbstractAuthenticationFilter.successfulAuthentication:
SecurityContextHolder.getContext().setAuthentication(authResult);
successHandler.onAuthenticationSuccess(request,response,authResult);

SecurityContextHolder

  1. SecurityContextPersistenceFilter
    在这里插入图片描述
    请求进来的时候,检查session里是否有SecurityContext,如果有则从session中取出,封装为SecurityContextHolder,放到线程环境中,没有则不处理;请求返回响应的时候,检查线程空间中是否有SecurityContext,有则取出放入session
  2. SecurityContextHolder
    封装ThreadLocal,是一个线程级的全局变量
  3. SecurityContext
    封装Authentication
  4. Authentication

6. 获取用户认证信息

  1. 在UserController中添加服务接口
@RestController
//@RequestMapping("/user")
public class UserController {
	@GetMapping("/user/me")
	public Object getCurrentUser() {
		return SecurityContextHolder.getContext().getAuthentication();
	}
	
	@GetMapping("/user/meAuthentication")
	public Object getCurrentAuthentication(Authentication authentication) {
		return authentication;
	}
	
	@GetMapping("/user/meUserDetails")
	public Object getCurrentUserDetails(UserDetails userDetails) {
		return userDetails;
	}
	.
	.
	.
}
  1. 登录用户名密码:http://localhost/index.html
  2. 访问:http://localhost/user/me
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值