Spring Security入门

发布时间:2024年01月13日

目录

1.简介 ????????

与shiro对比

添加依赖

执行流程

2.UserDetailsService

User实现类

3.PasswordEncoder

BCryptPasswordEncoder

4.自定义登录逻辑

5.自定义登录界面

6.设置请求账户和密码的参数

7.自定义登陆处理器

成功

失败

8.判断

权限判断

角色判断

ip判断

9.基于注解开发

@Secured

@PreAuthorize/@PostAuthorize

10.记住我功能

导入依赖

自定义

11.未完待续...


1.简介 ????????

Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需要选择适合的方式。此外,Spring Security还提供了一些附加功能,如集成第三方身份验证提供商和单点登录,以及会话管理和密码编码等。总之,Spring Security是一个强大且易于使用的框架,可以帮助开发人员提高应用程序的安全性和可靠性。

与shiro对比

Apache Shiro是一个强大且易用的Java安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

ssm+shrio

springBoot+springSecurity

添加依赖

<dependency>
 ? ?<groupId>org.springframework.boot</groupId>
 ? ?<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--web-->
<dependency>
 ? ?<groupId>org.springframework.boot</groupId>
 ? ?<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--test-->
<dependency>
 ? ?<groupId>org.springframework.boot</groupId>
 ? ?<artifactId>spring-boot-starter-test</artifactId>
 ? ?<scope>test</scope>
</dependency>

执行流程

2.UserDetailsService

通过传入的用户名来判断,如果要自定义逻辑来进行判断只需要实现UserDetailsService接口,而该接口又继承了UserDetails接口

public interface UserDetails extends Serializable {

	/**
	 * Returns the authorities granted to the user. Cannot return <code>null</code>.
	 * @return the authorities, sorted by natural key (never <code>null</code>)
	 */
	Collection<? extends GrantedAuthority> getAuthorities();

	/**
	 * Returns the password used to authenticate the user.
	 * @return the password
	 */
	String getPassword();

	/**
	 * Returns the username used to authenticate the user. Cannot return
	 * <code>null</code>.
	 * @return the username (never <code>null</code>)
	 */
	String getUsername();

	/**
	 * Indicates whether the user's account has expired. An expired account cannot be
	 * authenticated.
	 * @return <code>true</code> if the user's account is valid (ie non-expired),
	 * <code>false</code> if no longer valid (ie expired)
	 */
	boolean isAccountNonExpired();

	/**
	 * Indicates whether the user is locked or unlocked. A locked user cannot be
	 * authenticated.
	 * @return <code>true</code> if the user is not locked, <code>false</code> otherwise
	 */
	boolean isAccountNonLocked();

	/**
	 * Indicates whether the user's credentials (password) has expired. Expired
	 * credentials prevent authentication.
	 * @return <code>true</code> if the user's credentials are valid (ie non-expired),
	 * <code>false</code> if no longer valid (ie expired)
	 */
	boolean isCredentialsNonExpired();

	/**
	 * Indicates whether the user is enabled or disabled. A disabled user cannot be
	 * authenticated.
	 * @return <code>true</code> if the user is enabled, <code>false</code> otherwise
	 */
	boolean isEnabled();

}

User实现类

????????这个user不是我们自定义的User类,他继承了UserDetails接口,通过客户端传入的username在去数据库匹配对应的密码,然后通过查询出来的密码与前端进行匹配

public User(String username, String password, boolean enabled, boolean accountNonExpired,
            boolean credentialsNonExpired, boolean accountNonLocked,
            Collection<? extends GrantedAuthority> authorities) {
    Assert.isTrue(username != null && !"".equals(username) && password != null,
                  "Cannot pass null or empty values to constructor");
    this.username = username;
    this.password = password;
    this.enabled = enabled;
    this.accountNonExpired = accountNonExpired;
    this.credentialsNonExpired = credentialsNonExpired;
    this.accountNonLocked = accountNonLocked;
    this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}

3.PasswordEncoder

public interface PasswordEncoder {

	/**
	 * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
	 * greater hash combined with an 8-byte or greater randomly generated salt.
	 */
	String encode(CharSequence rawPassword);//加密密码
	/**
	 * Verify the encoded password obtained from storage matches the submitted raw
	 * password after it too is encoded. Returns true if the passwords match, false if
	 * they do not. The stored password itself is never decoded.
	 * @param rawPassword the raw password to encode and match
	 * @param encodedPassword the encoded password from storage to compare with
	 * @return true if the raw password, after encoding, matches the encoded password from
	 * storage
	 */
	boolean matches(CharSequence rawPassword, String encodedPassword);//原密码和加密密码进行匹配

	/**
	 * Returns true if the encoded password should be encoded again for better security,
	 * else false. The default implementation always returns false.
	 * @param encodedPassword the encoded password to check
	 * @return true if the encoded password should be encoded again for better security,
	 * else false.
	 */
	default boolean upgradeEncoding(String encodedPassword) {
		return false;
	}

}

BCryptPasswordEncoder

官方推荐的一个实现类,基于哈希算法进行加密

4.自定义登录逻辑

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.模拟数据库查询用户名是否存在否则抛出异常UsernameNotFoundException
        if(!"admin".equals(username)){
            throw new UsernameNotFoundException("用户名不存在!");
        }
        //2.把加密过的密码进行解析
        String password = passwordEncoder.encode("123");
        return new User(username,password,
                        AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

5.自定义登录界面

重定向时只能用@Controller,不能用@RestController

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            //当发现是login时认为是登录,必须和表单提供的地址一致去执行UserDetailsServiceImpl
            .loginProcessingUrl("/login")
            //自定义登录界面
            .loginPage("/login.html")
            //登录成功后必须是post请求
            .successForwardUrl("/toMain")
            //登录失败后跳转
            .failureForwardUrl("/toError");

        //认证授权
        http.authorizeRequests()
            //登录失败放行不需要认证
            .antMatchers("/error.html").permitAll()
            //登录放行不需要认证
            .antMatchers("/login.html").permitAll()
            //所有请求都被拦截类似于mvc必须登录后访问
            .anyRequest().authenticated();

        //关闭csrf防护
        http.csrf().disable();
    }
}
/**
     * 页面跳转
     * @return
     */
    @RequestMapping("/toMain")
    public String toMain(){
        return "redirect:main.html";
    }

    /**
     * 页面失败跳转
     * @return
     */
    @RequestMapping("/toError")
    public String toError(){
        return "redirect:error.html";
    }

6.设置请求账户和密码的参数

登录的表单name为username和password与UsernamePasswordAuthenticationFilter中相对应,如果我们想要修改这个属性名可以通过usernameParameter()和passwordParameter()进行修改

public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

7.自定义登陆处理器

由于前后端分离项目,页面跳转大多都在web浏览器中进行,这时successForwardUrl()和failureForwardUrl()方法就会失效,所以我们就要自定义方法进行实现且不能与successForwardUrl和failureForwardUrl共存

成功

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private String url;

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response
                                        , Authentication authentication) throws IOException, ServletException {
        User user = (User) authentication.getPrincipal();
        System.out.println(user.getAuthorities());
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());//密码被保护会输出null

        response.sendRedirect(url);
    }
}

失败

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private String url;

    public MyAuthenticationFailureHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

8.判断

权限判断

在自定义逻辑中创建的User中指定,严格区分大小写

return new User(username,password,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
.antMatchers("/main1.html").hasAnyAuthority("admin")

角色判断

return new User(username,password,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));
.antMatchers("/main1.html").hasAnyRole("abc")

ip判断

localhost和127.0.0.1的ip是不一样的需要指定特定的ip地址

9.基于注解开发

在启动类中添加注解@EnableGlobalMethodSecurity(securedEnabled = true)

@Secured

在controller层中添加

@Secured("ROLE_abc")
@RequestMapping("/toMain")
public String toMain(){
    return "redirect:main.html";
}

@PreAuthorize/@PostAuthorize

方法或类级别的注解

@PreAuthorize("hasRole('ROLE_abc')")

PreAuthorize表达式允许ROLE开头,也可以不用ROLE开头 ?

10.记住我功能

导入依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

自定义

在config中配置

@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private DataSource dataSource;
@Autowired
PersistentTokenRepository persistentTokenRepository;
http.rememberMe()
    .tokenValiditySeconds(60)
    //自定义登录逻辑
    .userDetailsService(userDetailsService)
    //持久层对象
    .tokenRepository(persistentTokenRepository);//底层靠jdbc实现
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    //第一次启动时建表,第二次使用时注释掉
    //jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
}

11.未完待续...

文章来源:https://blog.csdn.net/weixin_64704029/article/details/135575545
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。