其他分享
首页 > 其他分享> > spirng框架之spring security(一)

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