Spring Security学习第一天

Spring Security学习第一天

为什么学习springSecurity?

springSecrity是解决系统安全问题的框架。如果没有安全框架,我们需要手动处理每个资源的访问 控制,非常麻烦。使用安全框架,我们可以通过配置的方式实现对资源的访问限制。

常用的安全框架有哪些?

Spring Security:Spring家族一员。是一个能够为基于Spring的企业应用系统提供声明式的安全访 问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了 Spring IoC , DI(控制反转Inversion of Control,DI:Dependency Injection 依赖注入) 和 AOP(面向切面编程) 功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安 全控制编写大量重复代码的工作。

Apache Shiro:一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理。

为什么选择SpringSecurity?

SpringSecurity是spring全家桶的一员,SpringSecurity相比较shiro可以完成更多的需求

SpringSecurity两大概念"认证"和"授权"

认证:可以理解为登录,验证用户身份合法

授权:认证后不同用户拥有不同权限,资源是否访问由是否拥有权限决定

如何实现授权?

通常用RBAC实现,有RBAC基于角色的访问控制(Role-Based Access Control)是按角色进行授权,还有RBAC基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权,通常采用第二种,因为前者角色变换时需要改变相应权限,后者不需要


SpringSecurity快速入门

引入相应SpringSecurity,springboot相关依赖,启动启动类即可,无需编写任何代码。即可进入SpringSecurity自带的认证页面,因为SpringSecurity默认拦截所有请求。此时观察控制有由SpringSecurity打印的一串密码,默认用户名是user。此时便可完成登录。

UserDetailsService详解

当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,实现UserDetailsService 接口即可。

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

返回类型UserDetails又是一个接口,结构如下

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();//获取所有权限
    String getPassword();//获取密码
    String getUsername();//获取用户名
    boolean isAccountNonExpired();//账户是否过期
    boolean isAccountNonLocked();//账号是否被锁定
    boolean isCredentialsNonExpired();//凭证是否过期
    boolean isEnabled();//是否可用
}

所以我们选择使用Spring Security提供的实现类User返回,因为要实例化属性返回必须类接受


创建实现类实现UserDetailsService接口之前学习下PasswordEncoder 密码解析器

Spring Security 要求容器中必须有 PasswordEncoder 实例。所以当自定义登录逻辑时要求必须给容 器注入 PaswordEncoder 的bean对象。因此选择创建配置类,创建相应bean对象

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder pw(){
        return new BCryptPasswordEncoder();//官方推荐密码解析器实现类
    }
}

PasswordEncoder接口接口结构如下:

public interface PasswordEncoder {
    String encode(CharSequence var1);//单向加密
    boolean matches(CharSequence var1, String var2);
    //比较,第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }//二次加密,基本不使用
}

现在开始创建实现类实现UserDetailsService接口通过自定义逻辑控制认证逻辑,(以后再使用数据库)

@Service//问题是怎么检验密码呢,这里只能传入参数username
public class LoginServiceImpl implements UserDetailsService {
    @Autowired
    private PasswordEncoder pw;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.根据username查询数据库
        if(!"admin".equals(username)){
            throw new UsernameNotFoundException("用户名或密码错误");
        }
        //2.根据查询的对象比较密码
        String password = pw.encode("123456");
        //3.返回用户对象
        return new User("admin",password, AuthorityUtils.
                commaSeparatedStringToAuthorityList("admin,normal"));
    }

}

User类提供了两个构造方法

public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);
    }

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));
    }

我们选择第一个,username是用户名,password是密码,authorities是用户的权限,不允许为空。此处的用户名应该是客户端传递过来的用户名。而密码应该是从数据库中查询出来的密码。Spring Security 会根据 User 中的 password 和客户端传递过来的 password 进行比较。如果相同则表示认证 通过,如果不相同表示认证失败。

authorities 里面的权限对于后面学习授权是很有必要的,包含的所有内容为此用户具有的权限, 如有里面没有包含某个权限,而在做某个事情时必须包含某个权限则会出现 403。

通常都是通过AuthorityUtils.commaSeparatedStringToAuthorityList(“”) 来创建 authorities 集合对象 的。参数是一个字符串,多个权限使用逗号分隔。


自定义登录页面

实际项目中都是使用自定义登录页面,修改配置类便可完成。

修改配置类

修改配置类中主要是设置哪个页面是登录页面。配置类需要继承WebSecurityConfigurerAdapte,并重写 configure 方法。

successForwardUrl() :登录成功后跳转地址
loginPage() :登录页面
loginProcessingUrl :登录页面表单提交地址,此地址可以不真实存在。
antMatchers() :匹配内容
permitAll() :允许
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder pw(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()//表单登录
                .loginPage("/login.html")//自定义登录页面
                .loginProcessingUrl("/logindemo")//自定义登录逻辑
                .failureForwardUrl("/toError")
            	.successForwardUrl("/toMain");

        //授权
        http.authorizeRequests()
                //放行登录页面
                .antMatchers("/login.html").permitAll()
                .antMatchers("/error.html").permitAll()
                //所有请求都必须被认证
                .anyRequest().authenticated();
        //关闭csrf防护
        http.csrf().disable();
    }
}

因为success/FalilureForwardUrl底层使用请求转发,所以只能使用post方法,所以在这里再实现controller层

@Controller
public class LoginController {
        @PostMapping("/toMain")
        public String login(){
            return "redirect:main.html";
        }

        @PostMapping("/toError")
        public String error(){
            return "redirect:error.html";
        }
}

但是这不利于我们前后端分离,由于是请求转发,当遇到需要跳转到站外或在前后端分离的项目中就无法使用了。

所以我们可以进行自定义认证控制器,同样在配置类修改使用即可

比如:.successHandler(new MyAuthenticationSuccessHandler(“http://baidu.com”))

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    public final String url;

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

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse
            , Authentication authentication) throws IOException, ServletException {
        /*System.out.println(authentication.getAuthorities());//权限
        System.out.println(authentication.getCredentials());//密码,保护为null,凭证
        System.out.println(authentication.getDetails());//返回详情
        System.out.println(authentication.getPrincipal());//返回对象
        System.out.println(authentication.getName());*/
        httpServletResponse.sendRedirect(url);
    }
}

再给出比较重要的login.html代码。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/logindemo" method="post">
    用户名:<input type="text" name="username" /><br/>
    密码:<input type="password" name="password" /><br/>
    <input type="submit" value="登录" />
</form>
</body>
</html>

这里action提交地址应该与SecurityConfig中http.loginProcessingUrl()中地址相同,这里默认限制了用户名和密码必须为username,password

当进行登录之前会执行 UsernamePasswordAuthenticationFilter 过滤器。里面默认了用户名为username,密码为password。我们可以在配置类中自定义其他用户名和密码,比如

usernamePasrameter :账户参数名 
passwordParameter :密码参数名
postOnly=true :默认情况下只允许POST请求。
http.formLogin()
    .passwordParameter("password111")
	.usernameParameter("username123");
上一篇:Hbase java执行操作连接权限问题,org.apache.hadoop.hbase.security.AccessDeniedException: Insufficient permission


下一篇:Java调用第三方http接口的方式