ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

spirng框架之spring security(一)

2022-03-18 18:10:52  阅读:150  来源: 互联网

标签:spirng spring 用户 springframework org import security public


文章目录


一、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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有