【SpringSecurity】安全访问

一、描述

springSecurity应该清楚
该框架是行业之中最早的一款用于实现授权与认证检测的开发框架,随后出现了shiro框架,但是这些框架原始设计没有考虑到当今前后端分离的结构
springSecurity可以直接与springboot整合

二、编写启动类

在这里插入图片描述
程序启动日志:
Using generated security password: fe56a27e-85fd-4d5d-8286-a251fb5fe87d

在默认情况下如果直接启动包含“spring-boot-starter-security” 依赖库的项目,则在启动时会自动出现有一个临时的密码信息(每次启动密码读不一样),对应的用户名是“user”

三、创建controller类

在这里插入图片描述
浏览器访问:http://localhost:8080/message/show
会跳转到http://localhost:8080/login一个登录页面

在这里插入图片描述
登录之后才能看到结果
在这里插入图片描述
在访问某些资源的时候如果用户没有登录,那么就会要求用户登录

四、添加固定用户

application.yml文件中追加一个固定的账户
配置用户信息
仅仅依靠配置文件进行springSecurity的定义,那么基本上是不可能的,所以最佳的做肯定要基于Bean的模式完成

五、基于Bean配置SpringSecurity

需要创建有一个专属的配置类,而这个配置类必须继承“WebSecurityConfigurerAdapter”父类
密码加密处理
所有密码在进行处理的时候都必须提供一个密码编码器(PasswordEncoder),这个编码器所给出的类型是一个接口,而实现这个接口的就有若干种不同的加密处理形式
在这里插入图片描述

测试加密
![在这里插入图片描述](https://img-blog.csdnimg.cn/84e981170f994e24861e93aa71faba45.png
执行结果:在这里插入图片描述
配置认证信息
在这里插入图片描述
当前已经由application.yml的形式转为了原始的SpringSecurity的开发形式,整个配置更加灵活

六、前后端分离模块

1、按照默认情况下,SpringSecurity在进行认证检查的时候都是直接跳转到"/login"路径之中,但在前后分离模式下不可能见到这样的表单
2、配置授权访问
在这里插入图片描述
本配置程序类通过antMatchers()方法配置了授权访问路径,随后利用hasRole()或access()方法实现当前用户的权限认证,如果用户拥有相应权限则允许正常访问,否则跳转到错误页显示授权错误

七、返回Rest认证信息

所有认证信息以及授权错误信息都应该以REST数据的形式返回给客户端

设置认证成功
在这里插入图片描述
设置登录失败
在这里插入图片描述
设置注销

	.and().logout().logoutUrl("/logout")//设置注销地址
                .clearAuthentication(true) // 注销是清除认证信息
                .invalidateHttpSession(true) // 销毁当前session
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override //注销成功后返回内容
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.setContentType("application/json;charset=UTF-8");// 响应类型为JOSN
                        response.setStatus(HttpServletResponse.SC_OK);// 200状态码
                        Map<String, Object> result = new HashMap<>();// 响应数据
                        result.put("status",HttpServletResponse.SC_OK);// 当前状态
                        result.put("message","注销成功");
                        result.put("principal",null);
                        result.put("sessionId",request.getSession().getId());
                        //转换
                        ObjectMapper mapper = new ObjectMapper();
                        response.getWriter().println(mapper.writeValueAsString(result));
                    }
                })

利用curl命令发送post请求进行登录认证处理

curl -X POST -d "uname=admin&upass=123456" "http://localhost:80/login"

执行结果
在这里插入图片描述
传递cookie访问路径

curl -X GET -b "JSESSIONID=274C89C0FBDCDA795B67079894B13A7F" "http://localhost:80/message/show"

用户注销curl命令

curl -X GET -b "JSESSIONID=274C89C0FBDCDA795B67079894B13A7F" "http://localhost:80/logout"

执行结果
在这里插入图片描述

七、UserDetailsService

1、如果认证以及授权信息都希望通过程序来实现控制,那么就需要使用到UserDetailsService业务接口、对于当前不管使用哪种形式进行用户认证以及授权数据处理,那么都需要使用到“AuthenticationManagerBuilder”处理类,而在这个类中就提到了有一个使用UserDetailsService接口实例的配置方法:

 public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(T userDetailsService) throws Exception {
        this.defaultUserDetailsService = userDetailsService;
        return (DaoAuthenticationConfigurer)this.apply(new DaoAuthenticationConfigurer(userDetailsService));
    }

2、UserDetails观察下面接口:

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();//获取全部授权数据

    String getPassword();//获取密码

    String getUsername();//获取用户名

    boolean isAccountNonExpired();// 该账户是否没有过期

    boolean isAccountNonLocked();// 该账户是否没有锁定

    boolean isCredentialsNonExpired(); // 账户的认证数据没有过期

    boolean isEnabled();//该账户是否启用
}

3、UserDetails所保存的就是完整的认证信息,同时如果想要获取所有的授权信息则需要使用到GrantedAuthority接口

public interface GrantedAuthority extends Serializable {
    String getAuthority();
}

用户的每一个角色全部读通过GrantedAuthority对象实例进行保存,而后在UserDetails里面会通过一个集合来保存所有的授权的数据信息。
4、创建GrantedAuthority接口子类,主要保存用户的授权信息。

@Data
public class Role implements GrantedAuthority {
    private String rid;//角色id
    private String title;//名称

    @Override
    public String getAuthority() { //获取授权标记
        return this.rid;
    }
}

5、除了授权之外还需要考虑到使用认证信息,创建UserDetails接口子类

@Data
public class Member implements UserDetails {
    @Id
    private String mid;//用户id
    private String name;//姓名
    private String password;//密码
    private Integer enabled;//用户是否启用(1:true,0:false)
    private transient List<Role> roles;//保存全部角色

    @Override//获取授权数据
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.roles;
    }

    @Override//获取用户名
    public String getUsername() {
        return this.mid;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override//账后是否过期
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override//账户是否锁定
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override//认证是否失效
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override//启用状态
    public boolean isEnabled() {
        return this.enabled == 1;
    }
}

6、创建UserDetailsService接口子类,采用默认的用户名(admin),随后手工实现用户认证信息以及授权信息的配置。

@Service
//实现全部的认证与授权数据加载
public class UserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (!"admin".equals(username)){
            throw new UsernameNotFoundException("用户信息不存在!");//手工抛出异常
        }
        Member member = new Member();
        member.setMid("admin");
        member.setPassword("{bcrypt}$2a$10$BS3zEKnaZ8UYQKsu2QD89uz9.4qd2ZqHKywOMVAJLVODaIk6V4Cvm");
        member.setName("管理员");
        member.setEnabled(1);

        Role role = new Role();
        role.setRid("ROLE_ADMIN");//必须使用“ROLE_”开头
        role.setTitle("管理员");

        Role roleUser = new Role();
        roleUser.setRid("ROLE_USER");
        roleUser.setTitle("用户");
        member.setRoles(Arrays.asList(role,roleUser));
        return member;
    }
}

7、修改SecurityConfig配置类,注入UserdetailsService接口实例,并将该类实例配置到认证管理器中。


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


    @Bean // 如果想使用密码,则必须配置有一个密码的编码器
    public PasswordEncoder getPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();// 定义密码加密器
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {//进行访问配置
        http.authorizeRequests()// 进行访问路径以及对应角色
                .antMatchers("/admin/**").hasRole("ADMIN") //ADMIN角色可以访问
                .antMatchers("/member/**").access("hasAnyRole('USER')")//USER角色可以访问
                .antMatchers("/message/**").access("hasAnyRole('ADMIN') and hasAnyRole('USER')")
                .anyRequest().authenticated().and() //用户认证后允许访问
                .formLogin().loginProcessingUrl("/login") //登录处理路径
                .usernameParameter("uname").passwordParameter("upass")// 认证的参数名称
                .permitAll()
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        Object principal = authentication.getPrincipal();// 认证以及授权的内容
                        response.setContentType("application/json;charset=UTF-8");// 响应类型为JOSN
                        response.setStatus(HttpServletResponse.SC_OK);// 200状态码
                        Map<String, Object> result = new HashMap<>();// 登录成功的响应数据
                        result.put("status",HttpServletResponse.SC_OK);// 当前登录状态
                        result.put("message","登录成功");
                        result.put("principal",principal);
                        result.put("sessionId",request.getSession().getId());
                        //转换
                        ObjectMapper mapper = new ObjectMapper();
                        response.getWriter().println(mapper.writeValueAsString(result));
                    }
                }).failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        response.setContentType("application/json;charset=UTF-8");// 响应类型为JOSN
                        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);// 401状态码
                        Map<String, Object> result = new HashMap<>();// 登录失败的响应数据
                        result.put("status",HttpServletResponse.SC_UNAUTHORIZED);// 当前登录状态
                        result.put("principal",null);//返回一个空的认证数据
                        result.put("sessionId",request.getSession().getId());
                        if(e instanceof LockedException) result.put("message","账户被锁定,登录失败!");
                        else if(e instanceof BadCredentialsException) result.put("message","用户名或密码错误,登录失败!");
                        else if(e instanceof DisabledException) result.put("message","账户被禁用,登录失败!");
                        else if(e instanceof AccountExpiredException) result.put("message","账户已过期,登录失败!");
                        else if(e instanceof CredentialsExpiredException) result.put("message","密码已过期,登录失败!");
                        else result.put("message","未知原因,导致登录失败!");
                        //转换
                        ObjectMapper mapper = new ObjectMapper();
                        response.getWriter().println(mapper.writeValueAsString(result));
                    }
                })
                .and().logout().logoutUrl("/logout")//设置注销地址
                .clearAuthentication(true) // 注销是清除认证信息
                .invalidateHttpSession(true) // 销毁当前session
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override //注销成功后返回内容
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.setContentType("application/json;charset=UTF-8");// 响应类型为JOSN
                        response.setStatus(HttpServletResponse.SC_OK);// 200状态码
                        Map<String, Object> result = new HashMap<>();// 响应数据
                        result.put("status",HttpServletResponse.SC_OK);// 当前状态
                        result.put("message","注销成功");
                        result.put("principal",null);
                        result.put("sessionId",request.getSession().getId());
                        //转换
                        ObjectMapper mapper = new ObjectMapper();
                        response.getWriter().println(mapper.writeValueAsString(result));
                    }
                })
                .and() //开启登录表单路径
                .csrf().disable();  //关闭CSRF校验
    }
}

八、基于数据库实现认证授权

虽然通过UserDetailsService实现了认证与授权数据的存储,但是真正的开发是一定要通过数据库完成存储的

1、编写数据库创建脚本

-- 删除数据库、
drop database if exists springsecurity;

-- 创建数据库
create database springsecurity default character set utf8;
-- 使用数据库
use springsecurity;

-- 创建用户表(mid;登录id。name:真实姓名。password:登录密码。enabled:角色名称。)
-- enabled取值有两种:启用(enabled=1)、锁定(enabled=0)
create table member(
    mid varchar(50),
    name varchar(50),
    password varchar(68),
    enabled int(1),
    constraint pk_mid primary key(mid)
) engine=innodb;

-- 创建角色表(rid:角色id,也是权限授权检测的名称。title:角色名称。)
create table role (
    rid varchar(50),
    title varchar(50),
    constraint pk_rid primary key(rid)
) engine=innodb;

-- 创建用户-角色关联表(mid:用户id。rid:角色id。)
create table member_role
(
    mid varchar(50),
    rid varchar(50),
    constraint fk_mid foreign key (mid) references member(mid) on delete cascade,
    constraint fk_rid foreign key (rid) references role(rid) on delete cascade
) engine=innodb;

-- 添加用户数据(admin/123456、ran/123456)
insert into member values ('admin','张三','{bcrypt}$2a$10$BS3zEKnaZ8UYQKsu2QD89uz9.4qd2ZqHKywOMVAJLVODaIk6V4Cvm',1);
insert into member values ('ran','然纵','{bcrypt}$2a$10$BS3zEKnaZ8UYQKsu2QD89uz9.4qd2ZqHKywOMVAJLVODaIk6V4Cvm',0);
insert into member values ('root','李四','{bcrypt}$2a$10$BS3zEKnaZ8UYQKsu2QD89uz9.4qd2ZqHKywOMVAJLVODaIk6V4Cvm',1);

-- 添加角色数据
insert into role values ('ROLE_ADMIN','管理员');
insert into role values ('ROLE_USER','用户');

-- 添加用户与角色数据
insert into member_role values ('admin','ROLE_ADMIN');
insert into member_role values ('admin','ROLE_USER');
insert into member_role values ('ran','ROLE_ADMIN');
insert into member_role values ('root','ROLE_USER');

-- 提交事务
commit;

2、数据库信息
数据表信息

3、引入jpa相关依赖包

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.4.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>2.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>
    </dependencies>

4、修改Member类,追加Spring Data JPA注解

@Data
@Entity
@Table // 表名称与类名相同,可以写简单注解
@AllArgsConstructor
@NoArgsConstructor
public class Member implements UserDetails {
    @Id
    private String mid;//用户id
    private String name;//姓名
    private String password;//密码
    private Integer enabled;//用户是否启用(1:true,0:false)

    @ManyToMany(targetEntity = Role.class) // 启用延迟加载
    @JoinTable(name = "member_role",    // 关系表名称
            joinColumns = {@JoinColumn(name = "mid")},  //关联字段
            inverseJoinColumns = {@JoinColumn(name = "rid")})
    @JsonBackReference  // jackson防止数据递归处理
    private List<Role> roles;//保存全部角色

    @Override//获取授权数据
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.roles;
    }

    @Override//获取用户名
    public String getUsername() {
        return this.mid;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override//账后是否过期
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override//账户是否锁定
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override//认证是否失效
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override//启用状态
    public boolean isEnabled() {
        return this.enabled == 1;
    }
}

5、修改Role类,追加Spring Data JPA注解

@Data
@AllArgsConstructor
@NoArgsConstructor
@Table
@Entity
public class Role implements GrantedAuthority {
    @Id
    private String rid;
    private String title;

    @ManyToMany(mappedBy="roles") // 多对多关联
    @JsonBackReference     // Jackson防止数据递归处理
    private List<Member> members;

    @Override
    public String getAuthority() {//获取授权标记
        return this.rid;
    }
}

6、创建IMemberDAO接口

public interface IMemberDAO extends JpaRepository<Member,String> {
}

7、修改application.yml文件,配置druid数据源以及jpa

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springsecurity
    username: root
    password: 123456
  jpa:
    show-sql: true
    properties:
      hibernate:
        enable_lazy_load_no_trans: true #配置延迟加载

8、修改UserDetailsServiceImpl程序类,通过DAO接口实现数据查询

@Service
//实现全部的认证与授权数据加载
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private IMemberDAO iMemberDAO;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<Member> optional = this.iMemberDAO.findById(username);
        if (!optional.isPresent()) throw new UsernameNotFoundException("用户信息不存在!");
        return optional.get();
    }
}

9、修改程序启动类,追加JPA扫描配置注解

@SpringBootApplication
@EnableJpaRepositories("com.dz.dao")
@EntityScan("com.dz.vo")
public class StartSecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(StartSecurityApplication.class,args);
   }
}

此时用户登录时就可以通过IMemberDAO数据层接口实现基于数据库登录认证,对于开发者来讲只需要将数据以UserDetails接口实例的形式返回,就会由Spring Security自动实现后续的判断处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值