SpringSecurity-基于Web的认证与权限访问

目录

一、UserDetailsService接口

二、PasswordEncoder接口

三、从数据库认证实现登录

3.1 数据库表

3.2 实体类

3.3 Mapper、数据库配置

3.4 UserDetailsService实现类

3.5 主配置类

四、登录的相关配置

五、角色、权限的认证配置

六、自定义403页面

七、注解的使用

八、基于数据库的remeberMe

九、注销

十、CSRF


一、UserDetailsService接口

        当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。在查看认证过程源码的过程中也发现要关联UserDetailService完成认证操作。而在实际项目中账号和密码都是从数据库查询出来的。 所以我们要通过自定义逻辑控制认证逻辑

        UserDetailsService 接口的方法:

public interface UserDetailsService {
    //根据用户名获取用户的详细信息
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

        UserDetailsService实现类返回值UserDetails类型的,该接口中的如下:

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

        UserDetails的典型实现类-User

public class User implements UserDetails, CredentialsContainer {
... ...
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);
    }
//参数:名字、密码、权限的集合
//注:客户端传来的数据名字应该也是username、password
... ...
}

二、PasswordEncoder接口

public interface PasswordEncoder {

    String encode(CharSequence rawPassword);
    // 表示把参数按照特定的解析规则进行解析

    boolean matches(CharSequence rawPassword, String encodedPassword);
    // 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹
    //配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个
    //参数表示存储的密码。

    default boolean upgradeEncoding(String encodedPassword) {return false;}
    // 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回
    //false。默认返回 false。
}

        BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单 向加密。可以通过 strength 控制加密强度,默认 10.

        测试:

@Test
    void testEncoder(){
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        String text= "hello,wz!";

        //加密
        String encodedText = encoder.encode(text);
        System.out.println("加密后:"+encodedText);

        //判断加密前后数据是否一致
        System.out.println(encoder.matches(text, encodedText));
    }

结果:
加密后:$2a$10$BTmx9NhVz9LbqXboK3aTXOm7QDyFzXdlNiBMWg1.2L0MRMqqAmuEe
true

三、从数据库认证实现登录

3.1 数据库表

//用户表    (1,'wz',123456)(2,'zp',123456)
create table users(
     id bigint primary key auto_increment,
     username varchar(20) unique not null,
     password varchar(100)
);

//角色表    (1,'admin')(2,'user')
create table role(
    id bigint primary key auto_increment,
    name varchar(20)
);

//用户与角色绑定     (1,1)(2,2)
create table role_user(
    uid bigint,
    rid bigint
);

//权限表    (1,'insert')(2,'delete')(3,'update')(4,'select')
create table auth(
    id bigint primary key auto_increment,
    name varchar(20)
);

//权限与角色绑定     (1,1)(1,2)(1,3)(1,4)(2,4)
create table role_auth(
    rid bigint,
    aid bigint
);

3.2 实体类

@Data
public class Users {
    private Integer id;
    private String username;
    private String password;
}

3.3 Mapper、数据库配置

//@MapperScan("com.wz.springsecurity_web.mapper")  注意!此注解加在springboot启动类上
@Repository
public interface UsersMapper extends BaseMapper<Users> {
    String selectUserRole(String username);//获取角色
    List<String> selectUserAuths(String username);//获取权限
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wz.springsecurity_web.mapper.UsersMapper">

    <select id="selectUserRole" resultType="java.lang.String">
        SELECT r.name role FROM users u,role_user ru, role r
        WHERE u.`id`=ru.`uid`
          AND r.`id`=ru.`rid`
          AND u.`username`=#{username}
    </select>
    <select id="selectUserAuths" resultType="java.lang.String">
        SELECT a.name auth FROM users u,role_user ru, role r,role_auth ra,auth a
        WHERE u.`id`=ru.`uid`
          AND r.`id`=ru.`rid`
          AND r.`id`=ra.`rid`
          AND a.`id`=ra.`aid`
          AND u.`username`=#{username}
    </select>
</mapper>
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring_security
    username: root
    password: 310333

3.4 UserDetailsService实现类

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UsersMapper usersMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        Users users = usersMapper.selectOne(wrapper);//根据名字去数据库查询,看用户是否存在
        if (users == null) throw new UsernameNotFoundException("用户名不存在!");
        else {
            String role = usersMapper.selectUserRole(username);//获取角色
            List<String> auths = usersMapper.selectUserAuths(username);//获取权限

            List<GrantedAuthority> userAuths = new ArrayList<>();//创建一个存放权限与角色的集合
            userAuths.add(new SimpleGrantedAuthority("ROLE_" + role));//把角色加到集合中
            for (String auth : auths) userAuths.add(new SimpleGrantedAuthority(auth));//把权限加到集合中
            return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), userAuths);
        }
    }
}

3.5 主配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    //注入 PasswordEncoder 类到 spring 容器中
    @Bean
    PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
    //指定密码的编码器
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin();    // 表单登录
        http.authorizeRequests()     //认证配置
            .anyRequest()    // 任何请求
            .authenticated();    // 都需要身份验证
    }
}

        接下来在浏览器中输入 localhost:8080/hello ,输入数据库中的账户密码就成功登录了。

四、登录的相关配置

         我们可以在配置类中设置:

                登陆页面的资源路径

                登录请求路径

                登录成功的跳转路径

                指定哪些路径不被拦截

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()    // 表单登录
                .loginPage("/login.html")//登录页面路径
                .loginProcessingUrl("/user/login")//登录请求路径
                .defaultSuccessUrl("/index").permitAll();//登陆成功跳转路径
        http.authorizeRequests()     //认证配置
                .antMatchers("/hello","/user/login").permitAll() //指定的路径无需拦截
                .anyRequest()    // 其他任何请求
                .authenticated();    // 需要身份验证
        http.csrf().disable();//关闭csrf
    }

        login.html(resourse/static内)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="/user/login" method="post">
    user: <input type="text" name="username"/><br/>
    password:<input type="text" name="password"/><br/>
    <input type="submit" value="login"/>
</form>
</body>
</html>

        index.html(resourse/templates内)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <div>您已登录成功!欢迎来到首页!</div>
</body>
</html>

        控制器方法

 @GetMapping("/index")
    public String index(){
        return "index";
    }

        这样一来,我们想要进入index.html的话会自动跳转至我们自定义的登录页面login.html,登录成功后跳转回index.html

五、角色、权限的认证配置

        hasAuthority-        需要某个权限才可以访问

        hasAnyAuthority-        满足单个权限就可访问

        hasAnyRole-        需要某个角色才可以访问
        
hasRole-        满足单个角色就可访问

        还是在配置类中配置:

 @Override
 protected void configure(HttpSecurity http) throws Exception {
... ...
http.authorizeRequests()
           .antMatchers("/hello","/user/login").permitAll()//不需要验证的请求路径
           .antMatchers("/index").hasAnyAuthority("select","delete")//包含任何一个权限就可以访问这个路径
           //.antMatchers("/index").hasRole("admin")//当前主体要提供这个角色才可以访问
... ...

        如果不满足条件,会返回403的错误。

六、自定义403页面

        在配置类中配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
... ...
     http.exceptionHandling().accessDeniedPage("/unauth.html");//访问被拒绝时跳转的页面
... ...

        unauth.html(resources/static)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>警告</title>
</head>
<body>
    <div align="center"><h1>没有访问权限!</h1></div>
</body>
</html>

七、注解的使用

        @Secured

                判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀ROLE_“。

        首先在springboot启动类上加注解

@EnableGlobalMethodSecurity(securedEnabled=true)
@Secured({"ROLE_admin","ROLE_user"})
@GetMapping("/index")
    public String index(){
        return "index";
    }

        @PreAuthorize

                进入方法前权限验证

         首先在springboot启动类上加注解

@EnableGlobalMethodSecurity(prePostEnabled = true)
@PreAuthorize("hasAnyAuthority('select','delete')")
@GetMapping("/index")
    public String index(){
        return "index";
    }

         @PostAuthorize:

                方法执行后再进行权限验证.

        首先在springboot启动类上加注解

@EnableGlobalMethodSecurity(prePostEnabled = true)

        还有@PostFilter@PreFilter注解,可以对数据过滤 ,如

@PostFilter("filterObject.username == 'admin1'")
@PreFilter(value = "filterObject.id%2==0")

八、基于数据库的remeberMe

        创建数据库表

CREATE TABLE `persistent_logins` (
 `username` varchar(64) NOT NULL,
 `series` varchar(64) NOT NULL,
 `token` varchar(64) NOT NULL,
 `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP,
 PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

        修改主配置类

... ...
 @Autowired
    DataSource dataSource;//注入数据源
 @Bean
    PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository=new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);// 赋值数据源
        jdbcTokenRepository.setCreateTableOnStartup(false);//自动创建表,第一次执行会创建,以后要执行就要删除掉!
        return  jdbcTokenRepository;
    }

... ...
 @Override
 protected void configure(HttpSecurity http) throws Exception {
... ...
    http.rememberMe().tokenRepository(persistentTokenRepository())
                    .tokenValiditySeconds(60)//设置有效时长(s)
                    .userDetailsService(userDetailsService);
... ...

        在登陆页login.html表单中添加“记住我”选项

记住我:<input type="checkbox"name="remember-me"title="记住密码"/><br/>

        这样登录之后即使关闭浏览器,短时间内也可以不用重新登录

九、注销

        在index.html添加一个退出链接

<a href="/logout">退出</a>

        在配置类中添加退出映射地址

@Override
protected void configure(HttpSecurity http) throws Exception {
... ...
    http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll
... ...

        退出之后就需要重新登录了 

十、CSRF

        跨站请求伪造(英语:Cross-site request forgery)是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法,利用的是网站对用户网页浏览器的信任。

        跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个 自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买 商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。 这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的

        从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用 程序,Spring Security CSRF 会针对 PATCH,POST,PUTDELETE 方法进行防护。

        如何实现的

                1. 生成 csrfToken 保存到 HttpSession 或者 Cookie 中。

                2. 请求到来时,从请求中提取 csrfToken,和保存csrfToken 比较,进而判断当前请求是否合法。主要通过 CsrfFilter 过滤器来完成。

        若要开启这个功能,要从配置类删除字段

// http.csrf().disable();

        在登录页面添加一个隐藏域

<input type="hidden"th:if="${_csrf}!=null"th:value="${_csrf.token}"name="_csrf"/>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值