spirng框架之spring security(一)
作者:互联网
文章目录
-
目录
一、spring security 核心功能
用户认证(Authentication):系统判断用户是否能登录
用户授权(Authorization):系统判断用户是否有权限去做某些事情
攻击防护:防范CSRF攻击
权限管理中的相关概念:
名称 | 英文名称 | 概念 |
主体 | principal | 使用系统的用户或设备或从其他系统远程登录的用户等等 |
认证 | authentication | 权限管理系统(通过登录操作)确认一个主体的身份,允许主体进入系统。简单说就是“主体”证明自己是谁 |
授权 | authorization | 给用户分配权限:将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统中特定功能的能力 |
二、配置用户存储及自定义登录页
多年来出现了多钟配置spring security的方式,
● 有基于XML的配置;
● 使用方法注解的配置;
● 基于java的配置;
最近几个版本的spring security都支持基于java的配置。
spring security提供的多种配置用户存储的可选方案:
● 基于内存的用户存储
● 基于JDBC的用户存储
● 基于LDAP(轻量级目录访问协议)作为后端的用户存储
● 自定义用户详情服务(可基于数据库读取用户)
自定义登录页即:使用自己的登录页面替换spring security提供的默认登录页
三、防范CSRF攻击
CSRF(跨站请求伪造)是一种常见的安全攻击。让用户在一个恶意的WEB页面上填写信息,然后自动将表单以攻击受害者的身份提交到另外一个应用上。
spring security提供内置的CSRF保护,默认就是开启的。我们需要在页面表单中有一个名为“_csrf”的字段,他会持有CSRF token。服务端获取这个token与其记录的token对比来确保安全。
禁用spring security对CSRF支持:.and().csrf().disable();
四、退出及获取登录用户信息
● 退出
http.logout().logoutUrl("/logout").logoutSuccessUrl("/").permitAll();--会退出到根目录指定的地方
启用退出功能,需在HttpSecurity对象上调用logout方法,这样会搭建一个安全过滤器,此过滤器会拦截对"/logout"的请求。用户页面点击退出按钮后便会清空session。
● 了解登录用户是谁
常用的几种方式获取用户信息:
注入 Principal 对象到控制器方法中;
注入 Authentication 对象到控制器方法中;
使用 SecurityContextHolder 来获取安全上下文;
使用 @AuthenticationPrincipal 注解来标注方法;--最整洁易使用的方式
五、Spring Security基本原理
Spring Security本质是一个过滤器链,由许多过滤器组成,其中几个过滤器:
● FilterSecurityInterceptor:方法级的权限过滤器, 基本位于过滤链的最底部。
● ExceptionTranslationFilter:异常过滤器,用来处理在认证授权过程中抛出的异常
● UsernamePasswordAuthenticationFilter:对/login(Security默认的登录请求监听路径)的 POST请求做拦截,校验表单中用户名,密码。拦截表单中的用户名和密码封装成UsernamePasswordAuthenticationToken。然后完成UsernamePasswordAuthenticationToken和UserDetails密码的对比。
六、自定义用户详情服务基于数据库实现登录认证及授权
1. 启用Spring Security
添加Spring Security起步依赖到构建文件pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. 定义用户领域对象
实现UserDetails接口重写其属性或方法。不实现UserDetails也行,在自定义创建用户详情服务(MyUserDetailsService)类总采用第二种提供User对象的方式。SimpleGrantedAuthority("ROLE_USER")的角色ROLE_USER和第5步SecurityConfig类中antMatchers("/test/adduser","/test/findAllUser","/test/hello").hasRole("USER")这里指定的角色USER对应
package com.securitydemo.entity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Set;
/**
* 用户领域实体bean数据表映射
*/
@Entity
@Table(name = "SYS_USER", schema = "PRODUCTION", catalog = "")
public class User implements UserDetails {
private long id;
private String password;
private String username;
private Date addtime;
//实现UserDetails接口重写的属性或方法
//private Set<GrantedAuthority> authorities;//角色
private Boolean accountNonExpired;//账户没有过期
private Boolean accountNonLocked;//账户没有锁定
private Boolean credentialsNonExpired;//密码没有过期
private Boolean enabled;//账户可用
@SequenceGenerator(name = "generator", sequenceName = "SEQ_A_TBL", allocationSize = 1)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
@Column(name = "ID", unique = true, nullable = false, scale = 0)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Basic
@Column(name = "PASSWORD")
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Basic
@Column(name = "USERNAME")
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Basic
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "ADDTIME")
public Date getAddtime() {
return addtime;
}
public void setAddtime(Date addtime) {
this.addtime = addtime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User that = (User) o;
if (id != that.id) return false;
if (password != null ? !password.equals(that.password) : that.password != null) return false;
if (username != null ? !username.equals(that.username) : that.username != null) return false;
if (addtime != null ? !addtime.equals(that.addtime) : that.addtime != null) return false;
return true;
}
@Override
public int hashCode() {
int result = (int) (id ^ (id >>> 32));
result = 31 * result + (password != null ? password.hashCode() : 0);
result = 31 * result + (username != null ? username.hashCode() : 0);
result = 31 * result + (addtime != null ? addtime.hashCode() : 0);
return result;
}
//实现UserDetails接口重写的属性或方法
@Transient //@Transient注释意思是不会被Spring Data JPA框架序列化到数据库,单纯的作为一个临时字段,接收完数据后就暂且用不上了
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
// public void setAuthorities(Set<GrantedAuthority> authorities) {
// this.authorities = authorities;
// }
@Column(name = "ACCOUNTNONEXPIRED")
public Boolean getAccountNonExpired() {
return accountNonExpired;
}
public void setAccountNonExpired(Boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
@Column(name = "ACCOUNTNONLOCKED")
public Boolean getAccountNonLocked() {
return accountNonLocked;
}
public void setAccountNonLocked(Boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
@Column(name = "CREDENTIALSNONEXPIRED")
public Boolean getCredentialsNonExpired() {
return credentialsNonExpired;
}
public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
@Column(name = "ENABLED")
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
@Transient
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Transient
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Transient
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Transient
@Override
public boolean isEnabled() {
return enabled;
}
}
3. 使用JPA接口查询用户
package com.securitydemo.repository;
import com.securitydemo.entity.User;
import org.springframework.data.repository.CrudRepository;
/**
* spring data JPA继承CrudRepository接口,用于Spring Security登录用户认证查询
*/
public interface MyUserRepository extends CrudRepository<User, Long> {
/**
* 方法命名查询
* @param username 用户名
* @return
*/
User findByUsername(String username);
}
4. 自定义创建用户详情服务
实现security提供的UserDetailsService接口。用户登录时,security会调用loadUserByUsername方法去数据库查询用户数据。
一. 此处User对象是com.securitydemo.entity.User自定义实体领域,其中实现了UserDetails接口。 自定义User实体领域实现UserDetails接口可以扩展除UserDetails接口中几个属性以外的许多其他属性,如用户的添加时间,备注,手机号等属性。
二. 如果自定义User实体领域不继承或者实现UserDetails接口,也可以通过用户名查询出用户信息后填装到org.springframework.security.core.userdetails.User 默认提供的User对象中返回。
不管怎样返回User对象,最终都要返回一个UserDetails。
package com.securitydemo.service.impl;
import com.securitydemo.entity.User;
import com.securitydemo.repository.MyUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 自定义创建用户详情服务,实现security提供的UserDetailsService接口
*/
@Service
public class MyUserDetailsService implements UserDetailsService {
/**
* 注入UserRepository获取数据库用户信息
*/
private MyUserRepository myUserRepo;
/**
* MyUserDetailsService类通过构造器将MyUserRepository注入进来
* @param userRepo
*/
@Autowired
public MyUserDetailsService(MyUserRepository userRepo){
this.myUserRepo=userRepo;
}
/**
* 用户登录时,security会调用loadUserByUsername方法去数据库查询数据
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username==="+username);
//根据页面登录的用户名查询数据库用户信息进行身份认证start
/**
* 一.
* 此处User对象是com.securitydemo.entity.User自定义实体领域,其中实现了UserDetails接口。
* 自定义User实体领域实现UserDetails接口可以扩展除UserDetails接口中几个属性以外的许多其他属性,如用户的添加时间,备注,手机号等属性。
*
* 二.
* 如果自定义User实体领域不继承或者实现UserDetails接口,也可以通过用户名查询出用户信息后填装到org.springframework.security.core.userdetails.User
* 默认提供的User对象中返回。
* 不管怎样返回User对象,最终都要返回一个UserDetails。
*/
User user=myUserRepo.findByUsername(username);
System.out.println("user-====="+user);
//判断
if(user!=null){
System.out.println(user.getUsername()+"---====---"+user.getPassword()+"----"+user.getAddtime());
return user;
}
throw new UsernameNotFoundException(username+"用户名不存在!");
//根据页面登录的用户名查询数据库用户信息进行身份认证end
/**
* 二. 通过用户名查询出用户信息后填装到org.springframework.security.core.userdetails.User默认提供的User对象中返回。
*/
// //手动设置了权限及角色,也可以通过数据库查询获取
// List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("addUser,findAll,ADMIN,USER"); //配置权限及角色
// //根据页面登录的用户名查询数据库用户信息进行身份认证
// return new User(atblUserLv.getUsername(),atblUserLv.getPassword(),auths);
}
}
5. 保护WEB请求,用户认证与授权
启动@Configuration和@EnableWebSecurity注解
继承WebSecurityConfigurerAdapter类,使用AuthenticationManagerBuilder认证用户(将数据库中查询到的用户详情传递进去)HttpSecurity保护WEB请求,添加自定义登录页面与权限控制代码
package com.securitydemo.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
/**
* 自定义用户名密码
* 方式一:通通过配置类进行配置实现身份认证登录(基于内存的用户存储)
* 方式二:自定义实现类实现身份认证登录(基于数据库的用户存储)
*/
@Configuration //表明是一个配置类bean
@EnableWebSecurity //此注解1: 加载了WebSecurityConfiguration配置类, 配置安全认证策略。2: 加载了AuthenticationConfiguration, 配置了认证信息。
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 方式一:通过配置类进行配置实现身份认证登录(基于内存的用户存储)
* @param auth
* @throws Exception
*/
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// // 创建密码解析器
// BCryptPasswordEncoder pe =new BCryptPasswordEncoder();
// // 对密码进行加密
// String password = pe.encode("a123456");
// auth.inMemoryAuthentication()
// .passwordEncoder(pe) //默认没有,需要手动设置BCryptPasswordEncoder
// .withUser("user01")
// .password(password)
// .roles("admin")
// .and().withUser("user02").password(password).roles("user");
//
// }
/**
* 方式二:自定义实现类实现身份认证登录,实现类MyUserDetailsService根据页面登录的用户名从数据库查询用户进行判断
*/
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("SecurityConfig run...configure(AuthenticationManagerBuilder auth)");
/**
* 将自动装配到SecurityConfig中的UserDetailsService实例传递进去
* 也就是将从数据库查询到的用户详情传递进去
*/
auth.userDetailsService(userDetailsService);//.passwordEncoder(encoder());
}
/**
* 指定装备一个转码器使用注解@Bean
* @return
*/
@Bean
public PasswordEncoder encoder(){
return new BCryptPasswordEncoder();//进行转码 //NoOpPasswordEncoder.getInstance(); 不进行转码
}
/**
* 方式二:自定义实现类实现身份认证登录,继续添加自定义登录页面与权限控制代码
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("SecurityConfig run...configure(HttpSecurity http)");
//配置没有权限访问跳转自定义页面
//http.exceptionHandling().accessDeniedPage("/error.html");
//添加退出的映射地址
http.logout().logoutUrl("/logout").logoutSuccessUrl("/").permitAll();
http.authorizeRequests()
.antMatchers("/test/adduser","/test/findAllUser","/test/hello").hasRole("USER") //具备指定权限的用户且认证通过才能访问指定请求。hasRole方法中默认加有"ROLE_"前缀
.antMatchers("/","/**").permitAll() //其他请求允许所有用户访问,不需要认证 "/**"
.and().formLogin().loginPage("/logins") //登陆页面设置
.loginProcessingUrl("/user/login") //登陆访问路径,监听此路径来处理登录信息的提交
.defaultSuccessUrl("/test/findAllUser") //默认登录成功后跳转路径
.and().csrf().disable()
;
// http.formLogin() //自定义自己编写的登陆页面
// .loginPage("/logins.html") //登陆页面设置
// .loginProcessingUrl("/user/login") //登陆访问路径
// .defaultSuccessUrl("/test/hello").permitAll() //登陆成功后跳转路径
// .and().authorizeRequests() //授权
// .antMatchers("/","/user/login").permitAll() //设置那些路径可以直接访问,不需要认证
// .antMatchers("/test/adduser").hasAuthority("addUser") //当前用户只有具有addUser权限时才能访问该路径,需要在启动类或配置类中开启基于方法的安全认证机制
// .antMatchers("/test/findAllUser").hasAnyAuthority("addUser,findAll")//具备其中任意一个权限
.antMatchers("/test/hiRole").hasRole("admin")
.antMatchers("/test/hiAnyRole").hasAnyRole("admin,user")
// .anyRequest().authenticated() //任何请求都必须经过身份验证
// .and().csrf().disable() ; //关闭csrf的保护
}
}
6. 定义视图控制器,指定根路径和登录页
package com.securitydemo.Config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 定义视图控制器,访问根目录时跳转到指定页面
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
System.out.println("WebConfig run...");
registry.addViewController("/").setViewName("home");
registry.addViewController("/logins");
}
}
7. 提供测试控制器(Controller)
@AuthenticationPrincipal注解获取认证后登录的用户信息。
"/test/adduser","/test/findAllUser","/test/hello"这三个请求路径需要登录认证成功,且获得指定权限才能访问,其他两个方法未登录认证也可以访问。
package com.securitydemo.controller;
import com.securitydemo.entity.AtblUserLv;
import com.securitydemo.entity.User;
import com.securitydemo.service.UserService;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
/**
* 测试用控制器(Controller)
*/
@Controller //这个注解可返回显示视图页面。@RestController注解相当于@ResponseBody + @Controller合在一起的作用,方法返回字符串值
@RequestMapping("/test")
public class TestController {
@Resource
private UserService userService;
@GetMapping("/hello")
@ResponseBody //这个注解此方法表示返回字符串内容hello security
public String hello(@AuthenticationPrincipal User user){
System.out.println("了解登录用户是谁:"+user); //使用AuthenticationPrincipal注解,User是用户详情服务中的用户对象
if(user!=null){
System.out.println("了解登录用户是谁:"+user.getUsername()+"||"+user.getPassword()+"||"+user.getEnabled());
}
System.out.println("-----hello----");
return "hello security";
}
@GetMapping("/adduser")
public String addUser(@AuthenticationPrincipal User user){
System.out.println("了解登录用户是谁:"+user); //使用AuthenticationPrincipal注解,User是用户详情服务中的用户对象
if(user!=null){
System.out.println("了解登录用户是谁:"+user.getUsername()+"||"+user.getPassword()+"||"+user.getEnabled());
}
System.out.println("-----添加用户----");
AtblUserLv atblUserLv=new AtblUserLv();
atblUserLv.setUsername("吕亮");
atblUserLv.setPassword("12345");
atblUserLv.setAddtime(new Date());
userService.insertUser(atblUserLv);
return "success";
}
@GetMapping("/findAllUser")
public String findAllUsers(@AuthenticationPrincipal User user){
System.out.println("了解登录用户是谁:"+user); //使用AuthenticationPrincipal注解,User是用户详情服务中的用户对象
if(user!=null){
System.out.println("了解登录用户是谁:"+user.getUsername()+"||"+user.getPassword()+"||"+user.getEnabled());
}
List<AtblUserLv> list= userService.findAllUser();
Iterator<AtblUserLv> iter = list.iterator();
while (iter.hasNext()){
AtblUserLv atblUserLv=iter.next();
System.out.println(atblUserLv.getId()+"||"+atblUserLv.getUsername()+"||"+atblUserLv.getPassword());
}
return "success";
}
@GetMapping("/hiRole")
@ResponseBody //这个注解此方法表示返回字符串内容hello security
public String helloRole(){
System.out.println("-----helloRole----");
return "hello Role!";
}
@GetMapping("/hiAnyRole")
@ResponseBody //这个注解此方法表示返回字符串内容hello security
public String helloAnyRole(){
System.out.println("-----helloAnyRole----");
return "hello AnyRole!";
}
}
8. 相关页面
登录页logins.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
<!--注意:页面提交方式必须为 post 请求,用户名,密码必须为username,password
可以通过 usernameParameter()和 passwordParameter()方法修改默认配置-->
用户名:<input type="text" name="username">
<br/>
密码:<input type="text" name="password">
<br/>
<input type="submit" value="login">
</form>
</body>
</html>
主页home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>欢迎您的到来!</h3>
</body>
</html>
成功页success.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>success 欢迎你的到来!你成功访问此方法...</h3>
<br> <a href="/logout">退出</a>
</body>
</html>
错误页error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>error 欢迎你的到来!你没有权限访问...</h3>
</body>
</html>
9. 工程目录结构
总结
主要注意配置类SecurityConfig,用户详情实现类MyUserDetailsService和User类。
User类实现了UserDetails接口,MyUserDetailsService类查询数据库用户返回一个UserDetails详情对象User。UserDetails详情对象User装配到SecurityConfig(AuthenticationManagerBuilder)中与登录页面输入的用户名和密码进行匹配验证。SecurityConfig(HttpSecurity)进行自定义登录页面与权限控制的指定。
代码运行效果应该是:"/test/adduser","/test/findAllUser","/test/hello"这三个请求路径需要登录认证成功,且获得指定权限才能访问,其他两个方法未登录认证也可以访问。
标签:spirng,spring,用户,springframework,org,import,security,public 来源: https://blog.csdn.net/u011529483/article/details/123572622