spring security

文章目录

      • spring security工作原理简介
      • 样例
        • WebInitializer启动配置(相当于加入filter启动配置)
        • MVCInitializer启动全局配置
        • WebMvc配置
        • RootConfig配置
        • spring security配置(内存用户)
        • spring security配置(数据库用户)
        • SecurityContextHolder获取security context
        • 用户定义
        • 请求定义
        • https强制安全通道
        • 跨站请求伪造csrf
        • 启用remeber-me
        • 登陆
        • 登出
        • 方法鉴权
          • 方法一:
          • 方法二:
          • 方法三(使用springEL表达式):
          • 自定义权限计算器
        • 前端应用(spring security taglib)
        • 获取所有已经登陆的用户
        • 解密前端传来的加密后的密码
        • 调试时候遇见的常见错误

spring security工作原理简介

spring security和springmvc配合使用可以提供安全性,可以通过注册org.springframework.web.filter.DelegatingFilterProxy这个filter进行安全管理,也可以扩展AbstractSecurityWebApplicationInitializer类进行java方式的配置,AbstractSecurityWebApplicationInitializer可以自动配置DelegatingFilterProxy,DelegatingFilterProxy会拦截所有请求并将请求委托给id为springSecurityFilterChain的bean来进行处理(这里细节几乎不需要了解)

样例

WebInitializer启动配置(相当于加入filter启动配置)

public class WebInitializer extends AbstractSecurityWebApplicationInitializer {
}

MVCInitializer启动全局配置

public class MVCInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class<?>[] { RootConfig.class };
	}
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class<?>[] { WebMvcConfig.class };
	}
	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}

}

WebMvc配置

@Configuration
@EnableWebMvc
@ComponentScan("mvc.security.jsp")
public class WebMvcConfig extends WebMvcConfigurerAdapter {
}

RootConfig配置

@Configuration
// 排除EnableWebMvc注解下的所描路径
@ComponentScan(basePackages = { "mvc.security.jsp" }, excludeFilters = {
		@Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) })
public class RootConfig {
}

spring security配置(内存用户)

@Configuration
@EnableWebSecurity // 开启spring security配置
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	/**
	 * 配置用户,内存用户
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {// 用户认证配置
	auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN").and().withUser("user")
				.password("user").roles("USER");// 内存用户
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()// 表示通过登陆来实现验证,spring提供一个简单的默认登陆页面(如果不开启则默认登陆界面不出现)
				.and().authorizeRequests()// 开始请求权限配置
				.antMatchers("/admin").authenticated()// 只有通过验证的用户才可以访问
				.anyRequest().permitAll();// 其余任何请求无需验证
	}
}

spring security配置(数据库用户)

实体和dao:

@Entity
public class SysRole {
	@Id
	@GeneratedValue
	private Long id;
	private String name;
	/*get and set*/
}
@Entity
public class SysUser implements UserDetails {
	/**
	 * 
	 */
	private static final long serialVersionUID = -4452539189946701748L;

	@Id
	@GeneratedValue
	private Long id;
	private String username;
	private String password;
	@Transient
	private List<GrantedAuthority> auths;
	/*get and set*/
}

服务层UserDetailsService

@Component
public class CustomUserService implements UserDetailsService {
	@Autowired
	private SysUserRepository userRepository;
	@Autowired
	private SysRoleRepository roleRepository;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		SysUser user = userRepository.findByUsername(username);
		if (null == user) {
			throw new UsernameNotFoundException("'" + username + "'的用户名不存在");
		}
		
		List<SysRole> roles = roleRepository.findByUserId(user.getId());
		List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
		for (SysRole role : roles) {
			auths.add(new SimpleGrantedAuthority(role.getName()));
		}
		user.setAuths(auths);
		return user;
	}
}

WebSecurityConfig配置:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Bean
	public UserDetailsService customUserService() {
		return new CustomUserService();
	}

	/**
	 * 配置用户
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {// 用户认证配置
		auth.userDetailsService(customUserService());// 自定义数据库用户
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		/**
		 * 配置权限和url的映射
		 */
		http.formLogin()// 表示通过登陆来实现验证,spring提供一个简单的默认登陆页面(如果不开启则默认登陆界面不出现)
				.and().authorizeRequests()// 开始请求权限配置
				.antMatchers("/admin").authenticated()// 只有通过验证的用户才可以访问
				.anyRequest().permitAll();// 其余任何请求无需验证
	}
}

SecurityContextHolder获取security context

SecurityContextHolder可以获取context从而获取其他信息,比如权限,用户名等

public static String getUserName() {
		String userName;
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		if (authentication instanceof AnonymousAuthenticationToken) {
			userName = (String) authentication.getPrincipal();
		} else {
			UserDetails userDetails = (UserDetails) authentication.getPrincipal();
			if (null != userDetails) {
				userName = userDetails.getUsername();
			} else {
				userName = "";
			}
		}
		return userName;
	}

用户定义

内存用户可以使用withUser()方法返回UserDetailsBuilder对象,此对象可以定义用户的其他情况,方法如下:

方法 描述
accountExpired(boolean) 定义账户是否过期
accountLocked(boolean) 定义账户是否锁定
and() 用来连接配置
authorities(GrantedAuthority…) 授予用户一项或多项权限
authorities(List) 授予用户一项或多项权限
authorities(String…) 授予用户一项或多项权限
credentialsExpired(boolean) 定义凭证是否过期
disabled(boolean) 定义账号是否禁用
password(String) 定义用户密码
roles(String) 定义用户角色

数据库用户可以利用UserDetails接口,重写接口中的方法,返回相应数据,可以讲数据存入数据库,在重写方法时查询数据库某一个字段

public abstract interface UserDetails extends Serializable {
	public abstract Collection<? extends GrantedAuthority> getAuthorities();

	public abstract String getPassword();

	public abstract String getUsername();

	public abstract boolean isAccountNonExpired();

	public abstract boolean isAccountNonLocked();

	public abstract boolean isCredentialsNonExpired();

	public abstract boolean isEnabled();
}

请求定义

定义请求靠重写configure(HttpSecurity http)来实现,请求定义的核心方法如下:

方法 描述
antMatchers(String[] antPatterns) 匹配一个或多个url
antMatchers(HttpMethod method, String[] antPatterns) 匹配一个或多个url并匹配相应方法,多用于restful请求
anyRequest() 任意请求
regexMatchers(String[] antPatterns) 正则匹配一个或多个url
regexMatchers(HttpMethod method, String[] antPatterns) 正则匹配一个或多个url并匹配相应方法,多用于restful请求
@Override
protected void configure(HttpSecurity http) throws Exception {
	http.formLogin()// 表示通过登陆来实现验证,spring提供一个简单的默认登陆页面
				.and().authorizeRequests()// 开始请求权限配置
				.antMatchers("/admin").authenticated()
				.antMatchers("/bucs",HttpMethod.POST).authenticated()
				.regexMatchers("/bucs/.*").authenticated()
				.regexMatchers("/aucs/.*",HttpMethod.POST).authenticated()
				.anyRequest().permitAll()// 其余任何请求无需验证,anyRequest一定要放最后,先匹配小范围
				.and().anonymous();
}

以上方法返回AuthorizedUrl对象,AuthorizedUrl对象用来定义请求是否受限或者不受限,主要方法如下:

方法 描述
access(String) 给定的SpEL表达式为true,就允许访问
anonymous() 允许匿名用户访问
authenticated() 允许认证通过的用户访问
denyAll() 无条件拒绝所有用户访问
fullyAuthenticated() 完整认证(非remeber-me用户)则允许访问
hasAuthority(String) 具备给定权限则允许访问
hasRole(String) 具备给定角色则允许访问
hasAnyAuthority(String[]) 具备给定权限中的一个则允许访问
hasAnyRole(String[]) 具备给定角色中的一个则允许访问
hasIpAddress(String) 请求来自指定ip则允许访问
not() 对其他方法的结果求反
permitAll() 无条件允许
rememberMe() 允许remeber-me登陆用户访问
@Override
protected void configure(HttpSecurity http) throws Exception {
	http.formLogin()// 表示通过登陆来实现验证,spring提供一个简单的默认登陆页面
				.and().authorizeRequests()// 开始请求权限配置
				.antMatchers("/admin").permitAll()//对“/admin不进行认证”
				.antMatchers("/bucs",HttpMethod.POST).hasRole("ADMIN")
				.regexMatchers("/bucs/.*").hasAuthority("ROLE_ADMIN")//必须加ROLE_
				.regexMatchers("/aucs/.*",HttpMethod.POST).authenticated()
				.anyRequest().permitAll()// 其余任何请求无需验证
				.and().anonymous();
}

https强制安全通道

@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()// 表示通过登陆来实现验证,spring提供一个简单的默认登陆页面
				.and().authorizeRequests().antMatchers("/admin").hasRole("ADMIN")
				.and().requiresChannel()
				.antMatchers("/user").requiresSecure()// 需要安全通道
				.antMatchers("/admin").requiresInsecure();// 不需要安全通道,默认情况不需要安全通道
	}

跨站请求伪造csrf

开启:

@Override
protected void configure(HttpSecurity http) throws Exception {
	http.formLogin()// 表示通过登陆来实现验证,spring提供一个简单的默认登陆页面
				.and().authorizeRequests().antMatchers("/admin").hasRole("ADMIN")
				.and().csrf();
}

关闭:

@Override
protected void configure(HttpSecurity http) throws Exception {
	http.formLogin()// 表示通过登陆来实现验证,spring提供一个简单的默认登陆页面
				.and().authorizeRequests().antMatchers("/admin").hasRole("ADMIN")
				.and().csrf().disable();
}

启用remeber-me

这个功能是在在cookie中存储一个token来完成,token中包含用户名、密码、过期时间和一个私钥并且都进行类md5哈希,这里设定token时长为1天,私钥为helloKey

@Override
protected void configure(HttpSecurity http) throws Exception {
	http.formLogin()// 表示通过登陆来实现验证,spring提供一个简单的默认登陆页面
				.and().authorizeRequests()// 开始请求权限配置
				.antMatchers("/admin").hasRole("ADMIN")
				.and().rememberMe()//开启remeber-me
				.tokenValiditySeconds(24 * 60 * 60 * 1000)//设置token时长
				.key("helloKey");//设置key
}

登陆

formLogin()定义登陆

@Override
protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()
				.loginPage("/loginPage")// 定义登陆界面,这里登陆界面可以使用全url比如:http://localhost:8080/springmvc-security-jsp/loginPage
				.permitAll()// 这里一定要加permitAll,否则登陆界面无法访问
				.loginProcessingUrl("/login")// 登陆所用的url,即用户名密码等信息提交到的url,默认url是/login,但是自定义页面之后默认url不起作用,这里再写一遍
				.successHandler(new AuthenticationSuccessHandler() {//successHandler和defaultSuccessUrl只能配置一个,会相互覆盖,后配置的覆盖先配置的
					@Override
					public void onAuthenticationSuccess(HttpServletRequest arg0, HttpServletResponse arg1, Authentication arg2)
							throws IOException, ServletException {
						System.out.println("登陆成功");
						arg1.sendRedirect("loginSuccess");
					}
				})
//				.defaultSuccessUrl("/loginSuccess")// 登陆成功后跳转的url,也可以使用全url
				.failureHandler(new AuthenticationFailureHandler(){//failureHandler和failureUrl只能配置一个,会相互覆盖,后配置的覆盖先配置的
					@Override
					public void onAuthenticationFailure(HttpServletRequest paramHttpServletRequest,
							HttpServletResponse paramHttpServletResponse,
							AuthenticationException paramAuthenticationException) throws IOException, ServletException {
						System.out.println("登陆失败");
						paramHttpServletResponse.sendRedirect("loginFailure");
					}
				})
//				.failureUrl("/loginFailure")// 登陆失败后跳转的url,也可以使用全url
				.and().logout()// 定义登出
				.and().authorizeRequests()// 开始请求权限配置
				.antMatchers("/loginFailure").permitAll()//这里在定义登陆失败页面时需要不鉴权,否则登陆失败页面无法访问
				.antMatchers("/admin").hasRole("ADMIN").anyRequest().authenticated();
}
配置项 配置项说明
loginPage 登录页面,如果用户未指定,Security将提供默认的登录页面; 默认登录页面请求链接:/login
usernameParameter 用户名属性的名称;默认是username
passwordParameter 密码属性名称,默认是password
failureForwardUrl 授权失败时的跳转链接,实验时配置failureForwardUrl会导致登陆url无效
failureUrl 登录失败时的跳转链接,默认是/login?error
failureHandler 登录失败后的处理器
successForwardUrl 授权成功时的跳转链接,实验时配置successForwardUrl会导致登陆url无效
successHandler 登录成功时的处理器
defaultSuccessUrl 指定如果用户登录前未访问需要授权访问的页面,登录成功后的跳转链接
loginProcessingUr l 登录请求处理链接

前端代码:

<form name="form" action="login" method="post">
		
		<sec:csrfInput />
		<div class="form-group">
			<label for="username">账号label> <input type="text"
				class="form-control" name="username" value="" />
		div>
		<div class="form-group">
			<label for="password">密码label> <input type="password"
				class="form-control" name="password" value="" />
		div>

		<input type="submit" class="btn btn-primary" />
	form>

登出

logout()定义登出,和登入一样可以添加登出后需要做的事情,以及登出后需要跳转的url

@Override
protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()// 表示通过登陆来实现验证,spring提供一个简单的默认登陆页面
				.and().logout()// 定义登出
//				.logoutSuccessUrl("/logoutSuccess").permitAll()//此处一定更要使用permitAll,因为登出后没有权限无法访问登出成功界面,不过一般登出成功界面就是登陆界面在登陆界面permitAll就行
				.logoutSuccessHandler(new LogoutSuccessHandler(){//此处会覆盖logoutSuccessUrl的配置,如果使用logoutSuccessHandler需要给url“/logoutSuccess”开通permitAll
					@Override
					public void onLogoutSuccess(HttpServletRequest arg0, HttpServletResponse arg1, Authentication arg2)
							throws IOException, ServletException {
						System.out.println("登出成功");
						arg1.sendRedirect("logoutSuccess");
					}
				}).addLogoutHandler(new LogoutHandler(){//如果登出之前需要处理一些事务,可以在此接口中实现
					@Override
					public void logout(HttpServletRequest arg0, HttpServletResponse arg1, Authentication arg2) {
						System.out.println("登出前处理");
					}
				}).clearAuthentication(true)// 用户在退出后清除权限
				.invalidateHttpSession(true)// 用户在退出后Http session失效
				.and().authorizeRequests()// 开始请求权限配置
				.antMatchers("/loginFailure").permitAll()//这里在定义登陆失败页面时需要不鉴权,否则登陆失败页面无法访问
				.antMatchers("/logoutSuccess").permitAll()//这里在定义登出成功页面时需要不鉴权,否则登出成功页面无法访问
				.antMatchers("/admin").hasRole("ADMIN").anyRequest().authenticated();
}

前端代码:

<form name="form" action="logout" method="post">
		<input type="hidden" name="${_csrf.parameterName}"
			value="${_csrf.token}" /> <input type="submit"
			class="btn btn-primary" />
	form>

方法鉴权

方法一:

配置:

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}

添加权限

	@Secured(value = { "ROLE_USER" })
	public String getAdmin(){
		return "admin";
	}
方法二:

JSR-250的@RolesAllowed注解
配置:
jsr250Enabled为true可以使用@RolesAllowed注解

@Configuration
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}

添加权限

	@RolesAllowed(value = { "ADMIN" })
	public String getUser(){
		return "user";
	}
方法三(使用springEL表达式):

配置:
prePostEnabled为true可以使用以下注解:
@PreAuthorize :在方法调用之前,基于表达式的计算结果来限制对方法的访问
@PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
@PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果
@PreFilter 允许方法调用,但必须在进入方法之前过滤输入值

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled= true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}

添加权限

	@PreAuthorize("hasRole('USER')")//在方法前添加是否有USER角色,方法其实没有被调用
	public String getUser(){
		return "user";
	}

returnObject是springEL表达式的内置对象PostAuthorize中也可以使用returnObject来表示返回值

	@PostAuthorize("hasRole('USER')")//在方法前添加是否有USER角色,方法其实被调用了,但是会给浏览器返回403
	public String getUser(){
		return "user";
	}

过滤返回结果,id为1的允许返回

	@PostFilter("filterObject.id == 1")
    public List<Student> students(){//这里返回值必须是个集合或者数组对象,filterObject是springEL表达式的内置对象,表示过滤对象
        ArrayList<Student> list = new ArrayList<Student>();
        list.add(new Student(1,"123456"));
        list.add(new Student(2,"123456"));
        return list;
    }

过滤入参,id为1的允许进入

@PreFilter(filterTarget = "ids", value = "filterObject == 1")
	public String getUser(List<Integer> ids) {//这里入参必须是个集合或者数组对象filterObject也同上
		return "user." + ids;
	}
自定义权限计算器

配置:
这里需要重写createExpressionHandler方法,设置自定义权限计算器

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
	@Override
	protected MethodSecurityExpressionHandler createExpressionHandler() {
		DefaultMethodSecurityExpressionHandler handler = (DefaultMethodSecurityExpressionHandler) super.createExpressionHandler();
		handler.setPermissionEvaluator(new StudentPermissionEvaluator());
		return handler;
	}
}

自定义权限计算器:

public class StudentPermissionEvaluator implements PermissionEvaluator {
	@Override
	public boolean hasPermission(Authentication paramAuthentication, Object targetObjet, Object premission) {
		if (paramAuthentication.getName().equals("user")) {
			if (premission.equals("show")) {
				if (targetObjet.equals(2) || targetObjet.equals(3)) {
					return true;
				}
			}
		}
		return false;
	}
	// 如果目标对象的id可以得到,paramSerializable就是id,可以使用这个方法
	@Override
	public boolean hasPermission(Authentication paramAuthentication, Serializable paramSerializable, String paramString,
			Object paramObject) {
		throw new UnsupportedOperationException();
	}
}

添加权限

	@PreFilter("hasPermission(filterObject,'show')")//这里一定要用filterObject
	public String getUser(List<Integer> ids) {// 这里必须是个集合或者数组对象
		return "user." + ids;
	}

前端应用(spring security taglib)

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%
	String path = request.getContextPath();
	String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
			+ path + "/";
%>

<%@ taglib prefix="sec"
	uri="http://www.springframework.org/security/tags"%>
<html>
<head>
<base href="<%=basePath%>">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
	登陆成功
	<br>
	
	
	<sec:authentication property="principal.username"/><br>
	
	<sec:authentication property="principal.username" var="username1"/>
	<%=pageContext.getAttribute("username1") %><br>
	
	<sec:authentication property="principal.username" var="username2" scope="request"/>
	<%=request.getAttribute("username2") %><br>
	
	
	
	<sec:authorize access="hasRole('ADMIN')">ADMIN usersec:authorize><br>
	<sec:authorize access="hasRole('USER')">USER usersec:authorize><br>
	<sec:authorize access="isAuthenticated() and principal.username=='admin'">复杂表达式sec:authorize><br>
	
	<sec:authorize url="/admin">通过url权限来决定是否显示sec:authorize><br>
	
	<form name="form" action="logout" method="post">
		<input type="hidden" name="${_csrf.parameterName}"
			value="${_csrf.token}" /> <input type="submit"
			class="btn btn-primary" />
	form>
body>
html>

获取所有已经登陆的用户

获取用户主要使用SessionRegistry来进行,SessionRegistry可以获取session中的所有用户

	@Autowired
	private SessionRegistry sessionRegistry;
	
	@Bean
	public SessionRegistry sessionRegistry() {
		return new SessionRegistryImpl();
	}
	
	//注册sessionRegistry并设置最大session个数
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests().anyRequest().authenticated().and().sessionManagement().maximumSessions(10).sessionRegistry(sessionRegistry);
	}

	//使用sessionRegistry获取所有用户
	sessionRegistry.getAllPrincipals();

解密前端传来的加密后的密码

解密密码主要靠AuthenticationProvider来进行

	public class MyAuthenticationProvider extends DaoAuthenticationProvider {

	@Override
	protected void additionalAuthenticationChecks(UserDetails userDetails,
				UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
			String frountPassword = authentication.getCredentials().toString();//获取前端传递过来的密码(加密形式)
			//使用加解密算吗解密密码...
			
			super.additionalAuthenticationChecks(userDetails, authentication);
		}
	}
	@Autowired
	private AuthenticationProvider authenticationProvider;
	
	@Bean
	public AuthenticationProvider authenticationProvider() {
		MyAuthenticationProvider authenticationProvider = new MyAuthenticationProvider();
		authenticationProvider.setUserDetailsService(userDetailsService);
		authenticationProvider.setPasswordEncoder(passwordEncoder);
		return authenticationProvider;
	}

调试时候遇见的常见错误

302 FOUND:
这种错误是重定向,主要是因为访问的页面没有权限,所以springsecurity重定向到另外一个界面(一般是登陆界面)

你可能感兴趣的:(spring,mvc)