欢迎光临散文网 会员登陆 & 注册

动力节点SpringSecurity框架视频教程-springsecurity+

2023-04-19 14:41 作者:山药当当  | 我要投稿

P18-P26学习笔记

10 SpringSecurity 返回json

前后端分离成为企业应用开发中的主流,前后端分离通过json进行交互,登录成功和失败后不用页面跳转,而是一段json提示

10.1 新建模块09-springsecurity-json

10.2 添加依赖

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-security</artifactId>

</dependency>

10.3 新建三个controller和获取登录用户信息的controller

参照1.2.4 创建,可以直接拷贝过来

10.4 新建启动类

com.powernode下新建Application类,学员自行创建

10.5 创建统一响应类HttpResult

在com.powernode.vo中创建该类

@Data

@AllArgsConstructor

@NoArgsConstructor

@Builder

public class HttpResult {

    private Integer code;

    private String msg;

    private Object data;

    public HttpResult(Integer code, String msg) {

        this.code = code;

        this.msg = msg;

    }

}

10.6 创建登录成功处理器

com.powernode.config 包下创建

@Component

public class MyAutheticationSuccessHandle implements AuthenticationSuccessHandler {

@Resource

    private ObjectMapper objectMapper;


    @Override

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json;charset=utf-8");

        HttpResult httpResult = new HttpResult(200, "登录成功", authentication);

        String str = objectMapper.writeValueAsString(httpResult);

        response.getWriter().write(str);

        response.getWriter().flush();

    }

}

10.7 创建登录失败处理器

/**

 * 登陆失败的处理器

 */

@Component

public class AppAuthenticationFailureHandler implements AuthenticationFailureHandler {


    @Resource

    private ObjectMapper objectMapper;



    /**

     * @param request 当前的请求对象

     * @param response 当前的响应对象

     * @param exception 失败的原因的异常

     * @throws IOException

     * @throws ServletException

     */

    @Override

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        System.err.println("登陆失败");

        //设置响应编码

        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json;charset=utf-8");

        //返回JSON出去

        HttpResult result=new HttpResult(-1,"登陆失败");

        if(exception instanceof BadCredentialsException){

            result.setData("密码不正确");

        }else if(exception instanceof DisabledException){

            result.setData("账号被禁用");

        }else if(exception instanceof UsernameNotFoundException){

            result.setData("用户名不存在");

        }else if(exception instanceof CredentialsExpiredException){

            result.setData("密码已过期");

        }else if(exception instanceof AccountExpiredException){

            result.setData("账号已过期");

        }else if(exception instanceof LockedException){

            result.setData("账号被锁定");

        }else{

            result.setData("未知异常");

        }

        //把result转成JSON

        String json = objectMapper.writeValueAsString(result);

        //响应出去

        PrintWriter out = response.getWriter();

        out.write(json);

        out.flush();

    }

}

10.8 创键无权限处理器

/**

 * 无权限的处理器

 */

@Component

public class AppAccessDeniedHandler implements AccessDeniedHandler {


    //声明一个把对象转成JSON的对象

@Resource

    private ObjectMapper objectMapper;


    @Override

    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {

        //设置响应编码

        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json;charset=utf-8");

        //返回JSON出去

        HttpResult result=new HttpResult(-1,"您没有权限访问");

        //把result转成JSON

        String json = objectMapper.writeValueAsString(result);

        //响应出去

        PrintWriter out = response.getWriter();

        out.write(json);

        out.flush();

    }

}

10.9 创建登出(退出)处理器

/**

 * 退出成功的处理器

 */

@Component

public class AppLogoutSuccessHandler implements LogoutSuccessHandler {


    //声明一个把对象转成JSON的对象

@Resource

    private ObjectMapper objectMapper;


    /**

     *

     * @param request

     * @param response

     * @param authentication 当前退出的用户对象

     * @throws IOException

     * @throws ServletException

     */

    @Override

    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        System.out.println("退出成功");

        //设置响应编码

        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json;charset=utf-8");

        //返回JSON出去

        HttpResult result=new HttpResult(200,"退出成功");

        //把result转成JSON

        String json = objectMapper.writeValueAsString(result);

        //响应出去

        PrintWriter out = response.getWriter();

        out.write(json);

        out.flush();

    }

}

10.10 创建用户配置类

@Configuration

public class MySecurityUserConfig {

    @Bean

    public UserDetailsService userDetailService() {

//        使用org.springframework.security.core.userdetails.User类来定义用户

        //定义用户

        UserDetails user1 = User.builder()

                .username("eric")

                .password(passwordEncoder().encode("123456"))

                .roles("student")

                .build();

        UserDetails user2 = User.builder()

                .username("thomas")

                .password(passwordEncoder().encode("123456"))

                .roles("teacher")

                .build();

        UserDetails user3 = User.builder()

                .username("admin")

                .password(passwordEncoder().encode("123456"))

                .roles("admin")

                .build();

        //创建用户

        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();

        userDetailsManager.createUser(user1);

        userDetailsManager.createUser(user2);

        userDetailsManager.createUser(user3);

        return userDetailsManager;

    }


    /*

     * 从 Spring5 开始,强制要求密码要加密

     * @return

     */

    @Bean

    public PasswordEncoder passwordEncoder(){

        //使用加密算法对密码进行加密

        return new BCryptPasswordEncoder();

    }


}


10.11 安全配置类WebSecurityConfig

@Configuration

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    // 注入登陆成功的处理器

    @Autowired

    private AutheticationSuccessHandle successHandler;


    // 注入登陆失败的处理器

    @Autowired

    private AppAuthenticationFailureHandler failureHandler;


    // 注入没有权限的处理器

    @Autowired

    private AppAccessDeniedHandler accessDeniedHandler;


    //  注入退出成功的处理器

    @Autowired

    private AppLogoutSuccessHandler logoutSuccessHandler;


    @Override

    protected void configure(HttpSecurity http) throws Exception {

        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);

        http.formLogin().successHandler(successHandler).failureHandler(failureHandler).permitAll();

        http.logout().logoutSuccessHandler(logoutSuccessHandler);

        http.authorizeRequests().mvcMatchers("/teacher/**").hasRole("teacher").anyRequest().authenticated();

    }

}

10.12 启动程序

10.13 访问测试

可以使用admin用户实验登录失败、登录成功、退出和访问http://localhost:8080/teacher/query 查看无权访问的效果

11 使用UserDetailsService获取用户认证信息

11.1 新建子模块10-springsecurity-userdetailservice

11.2 添加依赖

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-security</artifactId>

</dependency>

11.3 新建启动类

com.powernode包下新建启动类Application,学员自行创建

11.4 新建三个controller

参照1.2.4 创建,可以直接拷贝过来

11.5 新建获取登录用户认证信息的controller

拷贝7.1 即可

11.6 新建用户信息类

com.powernode.vo包下新建SecurityUser 类,该类实现接口UserDetails接口

public class SecurityUser implements UserDetails {

    @Override

    public Collection<? extends GrantedAuthority> getAuthorities() {

        return null;

    }


    @Override

    public String getPassword() {

        //明文为123456

        return "$2a$10$KyXAnVcsrLaHMWpd3e2xhe6JmzBi.3AgMhteFq8t8kjxmwL8olEDq";

    }


    @Override

    public String getUsername() {

        return "thomas";

    }


    @Override

    public boolean isAccountNonExpired() {

        return true;

    }


    @Override

    public boolean isAccountNonLocked() {

        return true;

    }


    @Override

    public boolean isCredentialsNonExpired() {

        return true;

    }


    @Override

    public boolean isEnabled() {

        return true;

    }

}

代码说明:

用户实体类需要实现UserDetails接口,并实现该接口中的7个方法, UserDetails 接口的7个方法如下图:

11.7 新建类实现UserDetailService接口

com.powernode.service.impl 包下新建UserServiceImpl 实现UserDetailService

@Service

public class UserServiceImpl implements UserDetailsService {

    @Override

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SecurityUser securityUser= new SecurityUser();

        if(username==null || !username.equals(securityUser.getUsername())){

            throw new UsernameNotFoundException("该用户不存在");

        }

        return securityUser;

    }

}



11.8 新建安全配置类

com.powernode.config下新建WebSecurityConfig类,配置密码编码器

@Configuration

@Slf4j

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean

    public PasswordEncoder passwordEncoder(){

        return new BCryptPasswordEncoder();

    }

}

启动程序,并使用浏览器访问程序:http://localhost:8080/student/query

发现需要登录,使用thomas/123456 登录后,即可正常访问。

访问:http://localhost:8080/getLoginUserInfo

发现该用户并没有权限信息

11.9 配置用户权限信息

修改SecurityUser类中的getAuthorities 方法

    @Override

    public Collection<? extends GrantedAuthority> getAuthorities() {

       GrantedAuthority g1=()->"student:query"; //使用lambda表达式

//       GrantedAuthority g1=new SimpleGrantedAuthority("student:query");

       List<GrantedAuthority> grantedAuthorityList=new ArrayList<>();

       grantedAuthorityList.add(g1);

       return grantedAuthorityList;

    }

11.10 修改要访问controller中的方法需要哪些权限

修改WebSecurityConfig,添加全局方法拦截注解@EnableGlobalMethodSecurity(prePostEnabled = true)

注意可以去掉:@Configuration注解了


修改StudentController

添加 @PreAuthorize("hasAuthority('student:query')") 注解修改后如下:

@RestController

@RequestMapping("/student")

public class StudentController {

    @GetMapping("/query")

    @PreAuthorize("hasAuthority('student:query')")

    public String queryInfo(HttpServletRequest request){

        return "I am a student,My name is XXX";

    }

}

修改TeacherController

添加 @PreAuthorize("hasAuthority(teacher:query')") 注解修改后如下:

@RestController

@RequestMapping("/teacher")

public class TeacherController {

    @GetMapping("/query")

    @PreAuthorize("hasAuthority('teacher:query')")

    public String queryInfo(){

        return "I am a teacher,My name is Thomas";

    }

}


启动测试,使用thomas/123456 登录系统,发现可以访问student/query,不可以访问teacher/query

再次访问:http://localhost:8080/getLoginUserInfo 查看用户信息

11.11 为什么讲这个示例?

是为了使用数据库存储用户角色权限信息做准备,只要从数据库中取出数据存储到实现UserDetails 的接口的类中即可,比如SecurityUser 中即可。

12 基于数据库的认证

12.1 创建数据库security_study和表

创建数据库security_study

导入数据库脚本security_study.sql

12.2 创建模块11-springsecurity-database-authentication

12.3 添加依赖

<parent>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-parent</artifactId>

    <version>2.6.13</version>

</parent>


<properties>

    <maven.compiler.source>8</maven.compiler.source>

    <maven.compiler.target>8</maven.compiler.target>

    <java.version>8</java.version>

</properties>


<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-test</artifactId>

        <scope>test</scope>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-security</artifactId>

    </dependency>

    <dependency>

        <groupId>mysql</groupId>

        <artifactId>mysql-connector-java</artifactId>

        <scope>runtime</scope>

    </dependency>

    <dependency>

        <groupId>org.mybatis.spring.boot</groupId>

        <artifactId>mybatis-spring-boot-starter</artifactId>

        <version>2.2.2</version>

    </dependency>

    <dependency>

        <groupId>org.projectlombok</groupId>

        <artifactId>lombok</artifactId>

        <optional>true</optional>

    </dependency>

</dependencies>

<build>

    <plugins>

        <plugin>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-maven-plugin</artifactId>

        </plugin>

    </plugins>

</build>

12.4 配置数据源和mybatis

新建配置文件application.yml并配置数据源和mybatis

spring:

  datasource:

    driver-class-name: com.mysql.cj.jdbc.Driver

    url: jdbc:mysql://127.0.0.1:3306/security_study?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

    username: root

    password: root

mybatis:

  type-aliases-package: com.powernode.entity

  configuration:

    map-underscore-to-camel-case: true

    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

  mapper-locations: classpath:mapper/*.xml


12.5 新建各个包

12.6 新建用户实体类

com.powernode.entity 包下新建用户实体类

@Data

public class SysUser implements Serializable {

    private Integer userId;

    private String username;

    private String password;

    private String sex;

    private String address;

    private Integer enabled;

    private Integer accountNoExpired;

    private Integer credentialsNoExpired;

    private Integer accountNoLocked;


}

12.7 新建用户mapper和映射文件

com.powernode.dao下新建

public interface SysUserDao {

    /**

     * 根据用户名获取用户信息

     * @param username

     * @return

     */

    SysUser getByUserName(@Param("username") String username);

}

mapper下新建映射文件SysUserMapper.xml

<?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.powernode.dao.SysUserDao">

    <select id="getByUserName" resultType="sysUser">

        select user_id,username,password,sex,address,enabled,account_no_expired,credentials_no_expired,account_no_locked

        from sys_user where username=#{username}

    </select>

</mapper>

注意select后面不要使用*。

12.8 新建启动类

com.powernode包下新建启动类

@SpringBootApplication

@MapperScan("com.powernode.dao")

public class Application {

    public static void main(String[] args) {

        SpringApplication.run(Application.class,args);

    }

}

12.9 单元测试

测试dao

@SpringBootTest

class SysUserDaoTest {

    @Resource

    private SysUserDao sysUserDao;

    @Test

    void getByUserName() {

        SysUser sysUser = sysUserDao.getByUserName("obama");

        assertNotNull(sysUser);

    }

}


注意单元测试要测试哪些:dao--service-controller,实体类一般不需要测试

12.10 新建安全用户类

com.powernode.vo包下新建类

public class SecurityUser implements UserDetails {

    private  final SysUser sysUser;


    public SecurityUser(SysUser sysUser) {

        this.sysUser=sysUser;

    }


    @Override

    public Collection<? extends GrantedAuthority> getAuthorities() {

        return null;

    }


    @Override

    public String getPassword() {

        String userPassword=this.sysUser.getPassword();

//注意清除密码

this.sysUser.setPassword(null);

return userPassword;


    }


    @Override

    public String getUsername() {

        return sysUser.getUsername();

    }


    @Override

    public boolean isAccountNonExpired() {

        return sysUser.getAccountNoExpired().equals(1);

    }


    @Override

    public boolean isAccountNonLocked() {

        return sysUser.getAccountNoLocked().equals(1);

    }


    @Override

    public boolean isCredentialsNonExpired() {

        return sysUser.getCredentialsNoExpired().equals(1);

    }


    @Override

    public boolean isEnabled() {

        return sysUser.getEnabled().equals(1);

    }

}

12.11新建UserServiceImpl 实现UserDetailService接口

@Service

public class UserServiceImpl implements UserDetailsService {

    @Resource

    private SysUserDao sysUserDao;


    @Override

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SysUser sysUser = sysUserDao.getByUserName(username);

        if(null==sysUser){

            throw new UsernameNotFoundException("账号不存在");

        }

        return new SecurityUser(sysUser);

    }

}

12.12 新建service单元测试

@SpringBootTest

class UserServiceImplTest {

    @Resource

    private UserServiceImpl userService;

    @Test

    void loadUserByUsername() {

        UserDetails userDetails = userService.loadUserByUsername("obama");

        assertNotNull(userDetails);

    }

}

12.13 新建两个控制器

@RestController

@Slf4j

@RequestMapping("/student")

public class StudentController {

    @GetMapping("/query")

    public String queryInfo(){

        return "query student";

    }

    @GetMapping("/add")

    public String addInfo(){

        return "add  student!";

    }

    @GetMapping("/update")

    public String updateInfo(){

        return "update student";

    }

    @GetMapping("/delete")

    public String deleteInfo(){

        return "delete  student!";

    }

    @GetMapping("/export")

    public String exportInfo(){

        return "export  student!";

    }

}


@RestController

@Slf4j

@RequestMapping("/teacher")

public class TeacherController {

    @GetMapping("/query")

    @PreAuthorize("hasAuthority('teacher:query')")

    public String queryInfo(){

        return "I am a teacher!";

    }

}

12.14 新建获取登录用户认证信息的controller

从7.1 中拷贝即可

12.15 新建web安全配置类

@EnableGlobalMethodSecurity(prePostEnabled = true)

@Slf4j

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Bean

    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();

    }


    @Override

    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests().anyRequest().authenticated();

        http.formLogin();

    }

}

启动并进行各种测试

使用thomas和obama分别登录测试,发现student/query等能访问,teacher/query 不能访问,原因

http://localhost:8080/getLoginUserInfo


发现用户没有权限,但是/teacher/query 需要访问权限


动力节点SpringSecurity框架视频教程-springsecurity+的评论 (共 条)

分享到微博请遵守国家法律