SpringSecurity初体验

SpringSecurity初体验,第1张

SpringSecurity初体验

文章目录

实验一、引入spring security实验二、从mysql中加载用户

前置知识①、编写对象实现UserDetail接口②、实现UserDetailsService,这里使用了mysql+mybatis③、数据库配置和创建表:③、配置Spring Security Config④、编写controller⑤、登录加密

实验一、引入spring security

①、pom文件添加依赖


    org.springframework.boot
    spring-boot-starter-security


    org.springframework.security
    spring-security-test
    test

②、编写controller测试

@Controller
public class UserContorller {
    @GetMapping("/hello")
    @ResponseBody
    public  String hello(){
        return "hello hello" ;
    }
}

③、启动应用测试:localhost:8080/hello 发现需要经过spring security的登录认证路径 /login,其中用户名默认为user,密码是随机生成的uuid,打印在控制台中。

源码分析:
在UsernamePasswordAuthenticationFilter#attemptAuthentication方法中,已经定义了默认的username和password字段,用户提交上来的表单信息将在http

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    
    //创建路径匹配器
    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");
    private String usernameParameter = "username";
    private String passwordParameter = "password";
    private boolean postonly = true;

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
     else {
         // 从request中获取用户名和密码
            String username = this.obtainUsername(request);
            username = username != null ? username : "";
            username = username.trim();
            String password = this.obtainPassword(request);
            password = password != null ? password : "";
           //生成一个用户名和密码身份验证的令牌 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
         //设置身份请求的认证信息
        this.setDetails(request, authRequest);
         //返回一个经过身份验证的对象
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

// 从Request中获取参数
    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(this.passwordParameter);
    }

实验二、从mysql中加载用户 前置知识

spring security 不关心要登录的用户名称和密码从哪里来,它们可以来自配置文件,内存或者是数据库。

Spring Security 提供了一个配置类让我们配置它的主要行为: WebSecurityConfigurerAdapter
这个类有三个重要的重载函数,通过重写它们我们可以做许多工作,如定义spring security的访问规则,拦截规则,认证来源等等

方法描述configure (WebSecurity)配置spring security的filter链configure (HttpSecurity)配置如何通过拦截器保护请求configure (AuthenticationManagerBuilder)配置userDetail服务

⭐AuthenticationManagerBuilder
用来配置全局的认证相关的信息,核心是AuthenticationProvider和UserDetailsService,分别提供不同的认证方式(如密码+用户名或密码+邮箱/手机等)和自定义用户的细节。

这个类中有几个方法可以实现不同用户的存储,如InMemoryUserDetailsManagerConfigurer是基于内存的,JdbcUserDetailsManagerConfigurer是基于Jdbc方式的

而userDetailsService方法就是本实验的重点,允许我们自定义用户的实现。
它接收一个userDetailsService类的参数,返回一个DaoAuthenticationConfigurer

//AuthenticationManagerBuilder.java部分源码
    public InMemoryUserDetailsManagerConfigurer inMemoryAuthentication() throws Exception {
        return (InMemoryUserDetailsManagerConfigurer)this.apply(new InMemoryUserDetailsManagerConfigurer());
    }

    public JdbcUserDetailsManagerConfigurer jdbcAuthentication() throws Exception {
        return (JdbcUserDetailsManagerConfigurer)this.apply(new JdbcUserDetailsManagerConfigurer());
    }

    public  DaoAuthenticationConfigurer userDetailsService(T userDetailsService) throws Exception {
        this.defaultUserDetailsService = userDetailsService;
        return (DaoAuthenticationConfigurer)this.apply(new DaoAuthenticationConfigurer(userDetailsService));
    }

⭐HttpSecurity
用于权限规则的配置,提供了许多相关的方法,通过这些方法的组合,可以自定义登录,登出的url地址,页面,url拦截规则等等。

它被设计成链式调用,在执行每个方法后,都会返回一个预期的上下文,便于连续调用,不需要关心每个方法到底返回了什么

authorizeRequests()  //返回一个url拦截注册器,
//可以调用它提供的anyRequest(),antMachers(),和regexMatchers()等方法匹配系统的url
formLogin() 
httpBasic()
//这两个方法都声明了spring securtiy提供的表单认证方法,分别返回对应的配置器
csrf() //spring security提供的跨站请求伪造防护功能,默认开启
logout()	//登出相关
and() 

configure (HttpSecurity)的默认方法如下:

 protected void configure(HttpSecurity http) throws Exception {
     this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
     http.authorizeRequests((requests) -> {
         ((AuthorizedUrl)requests.anyRequest()).authenticated();
     });
     http.formLogin();
     http.httpBasic();
 }

可以看到它已经有一些默认的特性:

所有请求都需要认证允许表单登录进行身份验证允许用户使用http基本认证

知识点:
http基本认证是RFC2616中定义的一种认证模式,有4个步骤:

    客户端发起一条没有携带认证信息的请求服务器返回一条401Unauthorized响应,并在WWW-Authentication首部认证说明认证形式,当进行http基本认证后,WWW-Authentication会被设置为Basic realm=“被保护页面”客户端收到401 Unauthorized响应后,d出对话框,询问用户名和密码。当用户完成后,客户端将用户名和密码使用冒号拼接并编码为base64形式,然后放入请求Authorization首部发送给服务器。服务端解码从客户端发来的用户名和密码,并验证正确后,返回客户端请求的报文。

⭐WebSecurity
配置全局请求忽略规则,一般用来放行静态资源,如html,css,js等


回到实验,我们想要从Mysql数据库中查询出用户的信息给spring security校验,关键在于把得到的数据包装成它认识的样子,于是它提供了 UserDetailsService 和 UserDetails ,我们直接看源码:

UserDetail是个接口,定义了用户的信息

public interface UserDetails extends Serializable {
    Collection getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

UserDetailsService 也是个接口,而且只有一个方法,就是根据参数username来查找用户,并返回一个UserDetails 对象

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

实现UserDetails接口定义需要认证的用户,实现UserDetailsService定义如何从username获得用户对象。

①、编写对象实现UserDetail接口
@Data
public class User implements UserDetails {
    Long id ;
    String userName ;
    String passWord ;
    boolean enabled ;
    List authorities;

    @Override
    public Collection getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return passWord;
    }

    @Override
    public String getUsername() {
        return userName;
    }
//账户是否过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
//账户是否被锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

//    凭证是否过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
//用户是否可用
    @Override
    public boolean isEnabled() {
        return enabled;
    }
}
②、实现UserDetailsService,这里使用了mysql+mybatis
@Service
public class UserService implements UserDetailsService {

    @Autowired
    UserMapper userMapper ;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    //从数据库中查,下文有配置
        User user = userMapper.getUserByName(username);
        if(user==null) {
            throw new RuntimeException("用户不存在!");
        }
        return user ;
    }
}

③、数据库配置和创建表:
    pom文件添加依赖

	org.mybatis.spring.boot
	mybatis-spring-boot-starter
	2.2.1


		com.baomidou
		mybatis-plus-boot-starter
		3.5.0



	org.springframework.boot
	spring-boot-devtools
	runtime
	true


	com.alibaba
	druid-spring-boot-starter
	1.1.17


	mysql
	mysql-connector-java
	runtime

    配置数据库: 在application.properties文件中完成数据库的配置,注意替换成你自己的数据库信息
spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.url= jdbc:mysql:///${你的数据库名称}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username= root
spring.datasource.password=${你的数据库密码}
# 指定sql映射文件的位置
mybatis.mapper-locations = classpath*:com/wingchi/mapper/*.xml

    创建用户表,并插入数据
    (博主的用户表是项目中在用的,有一些其他的字段不需要理会)
CREATE TABLE `user` (
  `id` int(10) PRIMARY KEY AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `password` varchar(15) NOT NULL,
  `createTime` datetime,
  `lastLoginTime` datetime,
  `enabled` boolean NOT NULL
)ENGINE=InnoDB CHARSET=utf8;

insert into user values(1,'wingchi','234567',null,null,true) ;
insert into user values(2,'hk','123456',null,null,true) ;
    创建dao层,mybatis的知识点:
//UserMapper.java
@Mapper
public interface UserMapper {
    public User getUserByName(@Param("name") String name ) ;
}

UserMapper.xml (注意要和java文件同名!)





    
        select * from `user` where name = #{name}
    

③、配置Spring Security Config

回到实验的开头铺垫知识,我们需要重写WebSecurityConfigurerAdapter中的一些方法完成我们的配置

这里贴出我的代码:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(new UserService());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() // 禁用csrf 
        		.formLogin() // 开启http表单认证
        		.and() 
                .authorizeRequests()  // 身份认证请求
                .antMatchers("/hello/**").permitAll() 
                //任何匹配 /hello/**的路径,允许所有人访问
                .anyRequest().authenticated(); //其他的所有请求,都需要认证证。
    }
}

踩坑记录‍♀️: 我在配置的时候,因为不知道有http认证这个知识点,既没有配置 httpBasic()方式,也没有配置 formLogin()方式,这样是没办法正常运行的。 了解http的几种认证方式可以参考
这里

④、编写controller
@Controller
public class UserController {

    @RequestMapping("/user")
    @ResponseBody
    public String user(String name ) {
        return "hello"+name;
    }


    @RequestMapping("/hello")
    @ResponseBody
    public String hello() {
        return "hello";
    }
}
⑤、登录加密

从spring security5开始,就要求必须手动配置密码加密方式,spring官方推荐使用BCrypt加密,并明确指出sha和md5都是不安全的。所以这里使用BCrypt方式

配置十分简单:

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

spring security会自动应用这个bean。

参考:
https://blog.csdn.net/itguangit/article/details/78928886
https://blog.csdn.net/wangb_java/article/details/86676166
《spring security实战–陈木鑫》

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5719054.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存