认证--JSON

认证--JSON

课程计划

  • 登录成功/失败之后返回json字符串

  • 未登录错误提示

  • 退出登录json提示

  • 获取个人信息/修改个人信息

  • JSON登录

  • 手机号验证码登录

一、登录成功/失败返回JSON

1、修改第一个版本的代码

  • 直接编写返回的json字符串
复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {
​
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //http对象,支持链式调用
        //关闭csrf 跨域请求伪造的控制
        http.csrf(csrf -> csrf.disable());
        http.authorizeHttpRequests(
                auth ->
                        auth.requestMatchers("/loginpage.html", "/login/**")
                                .permitAll()
                                .anyRequest().authenticated()  //其他页面,要登录之后才能访问
        );//放过登录接口,以及静态页面
        //  ↓配置表单提交
        http.formLogin(form -> {
            form.loginPage("/loginpage.html")        //自定义登录页面的路径
                    .loginProcessingUrl("/javasmlogin")     //表单提交的路径
                    .usernameParameter("uname")              //自定义用户名的参数名(默认是username)
                    .passwordParameter("pwd")
                    //Authentication 是 UsernamePasswordAuthenticationToken-- principal实际的值,UserDetails
                    .successHandler(new AuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest request,
                                                            HttpServletResponse response,
                                                            Authentication authentication) throws IOException, ServletException {
                            response.setContentType("application/json;charset=utf-8");
                            R ok = R.ok(authentication);
                            //写出去
                            PrintWriter writer = response.getWriter();
                            writer.write(JSON.toJSONString(ok));
                            writer.flush();
                            writer.close();
                        }
                    })
                    //AuthenticationException 包含了 登录失败之后的 异常信息
                    .failureHandler(new AuthenticationFailureHandler() {
                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request,
                                                            HttpServletResponse response,
                                                            AuthenticationException exception) throws IOException, ServletException {
                            response.setContentType("application/json;charset=utf-8");
                            R error = R.error(exception.getMessage());
                            //写出去
                            PrintWriter writer = response.getWriter();
                            writer.write(JSON.toJSONString(error));
                            writer.flush();
                            writer.close();
                        }
                    })
                    .permitAll();     //以上提到的路径,都放行
        });
        //注销登录
        http.logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/loginpage.html")//注销成功之后,跳转的路径
                .permitAll()
        );
        return http.build();
​
    }
​
    @Bean
    public PasswordEncoder passwordEncoder() {
        //指定加密算法
        return new BCryptPasswordEncoder();
    }
}

2、优化代码

复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {
​
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //http对象,支持链式调用
        //关闭csrf 跨域请求伪造的控制
        http.csrf(csrf -> csrf.disable());
        http.authorizeHttpRequests(
                auth ->
                        auth.requestMatchers("/loginpage.html", "/login/**")
                                .permitAll()
                                .anyRequest().authenticated()  //其他页面,要登录之后才能访问
        );//放过登录接口,以及静态页面
        //  ↓配置表单提交
        http.formLogin(form -> {
            form.loginPage("/loginpage.html")        //自定义登录页面的路径
                    .loginProcessingUrl("/javasmlogin")     //表单提交的路径
                    .usernameParameter("uname")              //自定义用户名的参数名(默认是username)
                    .passwordParameter("pwd")
                    //Authentication 是 UsernamePasswordAuthenticationToken-- principal实际的值,UserDetails
                    .successHandler((request, response, authentication) ->
                            createSuccessJson(response,authentication)
                    )
                    //AuthenticationException 包含了 登录失败之后的 异常信息
                    .failureHandler((request, response, exception) ->
                            createFailJson(response,exception)
                    )
                    .permitAll();     //以上提到的路径,都放行
        });
        //注销登录
        http.logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/loginpage.html")//注销成功之后,跳转的路径
                .permitAll()
        );
        return http.build();
​
    }
​
    @Bean
    public PasswordEncoder passwordEncoder() {
        //指定加密算法
        return new BCryptPasswordEncoder();
    }
​
    private void createSuccessJson(HttpServletResponse response,Object object){
        R ok = R.ok(object);
        createJson(response,ok);
    }
​
    private void createFailJson(HttpServletResponse response,AuthenticationException e){
        R error = R.error(e.getMessage());
        createJson(response,error);
    }
​
    private void createJson(HttpServletResponse response,R r){
        try {
            response.setContentType("application/json;charset=utf-8");
            //写出去
            PrintWriter writer = response.getWriter();
            writer.write(JSON.toJSONString(r));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

二、配置未登录

异常信息的处理

未配置的情况下,会拦截请求,跳转到登录页面

复制代码
http.exceptionHandling().authenticationEntryPoint((request,response,e)->
                createFailJson(response,"当前用户未登录,请先登录再访问"));

三、退出登录

复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {
​
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //http对象,支持链式调用
        //关闭csrf 跨域请求伪造的控制
        http.csrf(csrf -> csrf.disable());
        http.authorizeHttpRequests(
                auth ->
                        auth.requestMatchers("/loginpage.html", "/login/**")
                                .permitAll()
                                .anyRequest().authenticated()  //其他页面,要登录之后才能访问
        );//放过登录接口,以及静态页面
        //  ↓配置表单提交
        http.formLogin(form -> {
            form.loginPage("/loginpage.html")        //自定义登录页面的路径
                    .loginProcessingUrl("/javasmlogin")     //表单提交的路径
                    .usernameParameter("uname")              //自定义用户名的参数名(默认是username)
                    .passwordParameter("pwd")
                    //Authentication 是 UsernamePasswordAuthenticationToken-- principal实际的值,UserDetails
                    .successHandler((request, response, authentication) ->
                            createSuccessJson(response,authentication)
                    )
                    //AuthenticationException 包含了 登录失败之后的 异常信息
                    .failureHandler((request, response, exception) ->
                            createFailJson(response,exception)
                    )
                    .permitAll();     //以上提到的路径,都放行
        });
        //注销登录
        http.logout(logout -> logout
                .logoutUrl("/logout")
                //退出登录的时候,返回用户信息
                .logoutSuccessHandler((r,response,a)->createSuccessJson(response,"退出登录成功!"))
                .permitAll()
        );
        //未登录异常提示
        http.exceptionHandling().authenticationEntryPoint((request,response,e)->
                createFailJson(response,"当前用户未登录,请先登录再访问"));
        return http.build();
​
    }
​
    @Bean
    public PasswordEncoder passwordEncoder() {
        //指定加密算法
        return new BCryptPasswordEncoder();
    }
​
    private void createSuccessJson(HttpServletResponse response,Object object){
        R ok = R.ok(object);
        createJson(response,ok);
    }
​
    private void createFailJson(HttpServletResponse response,AuthenticationException e){
        R error = R.error(e.getMessage());
        createJson(response,error);
    }
    private void createFailJson(HttpServletResponse response,String e){
        R error = R.error(e);
        createJson(response,error);
    }
​
    private void createJson(HttpServletResponse response,R r){
        try {
            response.setContentType("application/json;charset=utf-8");
            //如果想修改返回的状态码,可以这么修改
            //response.setStatus(r.getCode());
            //写出去
            PrintWriter writer = response.getWriter();
            writer.write(JSON.toJSONString(r));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

四、获取当前登录的用户信息

已经登录之后,获取当前登录的用户信息

复制代码
    @GetMapping("/my")
    public R queryMyUser(){
        LoginUserDetails loginUserDetails = loginService.getLoginUser();
        return R.ok(loginUserDetails);
    }
复制代码
    @Override
    public LoginUserDetails getLoginUser() {
        //查询已经登录的用户信息
        //获取Security上下文对象
        SecurityContext context = SecurityContextHolder.getContext();
        //从上下文对象中,获取用户信息。UsernamePasswordAuthenticationToken
        Authentication authentication = context.getAuthentication();
        if (authentication == null){
            throw new JavasmException(ExceptionEnum.Not_Login);
        }
        //principal  未登录成功之前,在UsernamePasswordAuthenticationFilter中,赋值是用户名
        //登录成功之后,在DaoAuthenticationProvider中,赋值是UserDetails
        Object principal = authentication.getPrincipal();
        if (principal instanceof LoginUserDetails){
            LoginUserDetails loginUserDetails = (LoginUserDetails) principal;
            return loginUserDetails;
        }else {
            throw new JavasmException(ExceptionEnum.Not_Login);
        }
    }

五、修改当前登录的用户信息

复制代码
@PutMapping("/update/loginuser")
public R updateLoginUser(AdminUser adminUser){
    loginService.updateLoginUser(adminUser);
    return R.ok();
}
复制代码
    @Override
    public void updateLoginUser(AdminUser adminUser) {
        //修改数据
        //先获取个人信息,当前登录的用户数据
        LoginUserDetails loginUser = getLoginUser();
        if (loginUser != null){
            AdminUser loginAdminUser = loginUser.getAdminUser();
            Integer uid = loginAdminUser.getUid();
            adminUser.setUid(uid);
            //密码
            if (!StringUtils.isEmpty(adminUser.getPassword())){
                //如果传了 密码,密码需要加密之后再保存到数据库
                String encodePassword = passwordEncoder.encode(adminUser.getPassword());
                adminUser.setPassword(encodePassword);
            }
            //存储成功
            adminUser.updateById();
            //上面只是修改了数据库的信息,同步Security中的用户信息
            //更新的对象 adminuser中不一定包括所有的字段,从数据库中,查询出最新的数据,同步到Security中
            AdminUser newAdminUser = adminUserService.getById(uid);
            LoginUserDetails loginUserDetails = new LoginUserDetails(newAdminUser);
            UsernamePasswordAuthenticationToken authentication =
                    UsernamePasswordAuthenticationToken.authenticated(
                            loginUserDetails,                           //最新的登录用户信息
                            newAdminUser.getPassword(),                 // 最新的密码
                            loginUser.getAuthorities()                  //授权列表
                    );
            //获取上下文对象--并把新的Authentication对象存储起来
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
​
    }

六、JSON格式登录

1、普通版本

确认一下登录接口的名字和数据格式

复制代码
/jsonlogin
复制代码
{
    "username":"test11",
    "password":"123"
}

这个请求方式,本质上,依然是用户名和密码登录,唯一改变的是获取用户名和密码的方式

1.1 新建一个Filter
复制代码
public class JsonFilter extends UsernamePasswordAuthenticationFilter {
    public static final String SPRING_SECURITY_JSON_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_JSON_PASSWORD_KEY = "password";
    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else if (!request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){
            throw new AuthenticationServiceException("请求的类型不对,应该是JSON格式");
        } else {
            //request对象中,可以获取用户名和密码
            ServletInputStream inputStream = request.getInputStream();
            //字节流转成字符流
            InputStreamReader streamReader = new InputStreamReader(inputStream);
            //转成字符流
            BufferedReader bufferedReader = new BufferedReader(streamReader);
            //存储字符的StringBuffer
            StringBuffer jsonBuffer = new StringBuffer();
            String  line;
            while ((line = bufferedReader.readLine()) != null){
                jsonBuffer.append(line);
            }
            //用户传入的json字符串
            String json = jsonBuffer.toString().trim();
            if (StringUtils.isEmpty(json)){
                throw new AuthenticationServiceException("参数为空");
            }
            //把数据转成map
            Map<String,String> map = JSONObject.parseObject(json, Map.class);

            String username = map.get(SPRING_SECURITY_JSON_USERNAME_KEY);
            username = username != null ? username.trim() : "";
            String password = map.get(SPRING_SECURITY_JSON_PASSWORD_KEY);
            password = password != null ? password : "";
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
}
1.2 把JsonFilter 配置到过滤器链中
  • 修改配置类
复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    //****************JSON登录--开始****************//
    @Resource(name = "usernameLoginUserDetailsService")
    UserDetailsService userDetailsService;

    @Bean
    public DaoAuthenticationProvider jsonProvider() {
        DaoAuthenticationProvider authenticationProvider =
                new DaoAuthenticationProvider();
        //配置  由哪个UserDetailsService负责查询用户信息UserDetails
        authenticationProvider.setUserDetailsService(userDetailsService);
        //配置密码的加密方式
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }

    //JSON等于专属的AuthenticationManager
    @Bean
    public AuthenticationManager jsonAuthenticationManager() {
        return new ProviderManager(List.of(jsonProvider()));
    }

    //配置JsonFilter
    public JsonFilter jsonFilter() {
        JsonFilter jsonFilter = new JsonFilter();
        //配置 AuthenticationManager
        jsonFilter.setAuthenticationManager(jsonAuthenticationManager());
        //json登录成功之后的显示
        jsonFilter.setAuthenticationSuccessHandler(this::createSuccessJson);
        //json登录失败之后,
        jsonFilter.setAuthenticationFailureHandler(
                (r, response, e) ->
                        createFailJson(response, e)
        );
        //配置json登录的路径
        jsonFilter.setFilterProcessesUrl("/jsonlogin");
        return jsonFilter;
    }
    
    @Bean
    public SecurityContextRepository securityContextRepository() {
        return new HttpSessionSecurityContextRepository();
    }
    //****************JSON登录--结束****************//
    

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.securityContext(context -> context.securityContextRepository(securityContextRepository()));
        //因为当前json登录功能,和用户名密码登录功能类似,
        // 所以把jsonfilter放到UsernamePasswordAuthenticaitonFilter相同的位置
        http.addFilterAt(jsonFilter(), UsernamePasswordAuthenticationFilter.class);
        //关闭csrf 跨域请求伪造的控制
        http.csrf(csrf -> csrf.disable());
        http.authorizeHttpRequests(
                auth ->
                        auth.requestMatchers("/loginpage.html", "/login/**")
                                .permitAll()
                                .anyRequest().authenticated()  //其他页面,要登录之后才能访问
        );//放过登录接口,以及静态页面
        //  ↓配置表单提交
        http.formLogin(form -> {
            form.loginPage("/loginpage.html")        //自定义登录页面的路径
                    .loginProcessingUrl("/javasmlogin")     //表单提交的路径
                    .usernameParameter("uname")              //自定义用户名的参数名(默认是username)
                    .passwordParameter("pwd")
                    //Authentication 是 UsernamePasswordAuthenticationToken-- principal实际的值,UserDetails
                    .successHandler(this::createSuccessJson)
                    //AuthenticationException 包含了 登录失败之后的 异常信息
                    .failureHandler((request, response, exception) ->
                            createFailJson(response, exception)
                    )
                    .permitAll();     //以上提到的路径,都放行
        });
        //注销登录
        http.logout(logout -> logout
                .logoutUrl("/logout")
                //退出登录的时候,返回用户信息
                .logoutSuccessHandler((r, response, a) -> createSuccessJson(response, "退出登录成功!"))
                .permitAll()
        );
        //未登录异常提示
        http.exceptionHandling().authenticationEntryPoint((request, response, e) ->
                createFailJson(response, "当前用户未登录,请先登录再访问"));
        return http.build();

    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        //指定加密算法
        return new BCryptPasswordEncoder();
    }
    
    private void createSuccessJson(HttpServletRequest request,HttpServletResponse response, Authentication authentication) {
        SecurityContextHolderStrategy strategy = SecurityContextHolder.getContextHolderStrategy();
        strategy.getContext().setAuthentication(authentication);
        securityContextRepository().saveContext(strategy.getContext(), request, response);
        createSuccessJson(response,authentication);
    }
    private void createSuccessJson(HttpServletResponse response, Object object) {
        R ok = R.ok(object);
        createJson(response, ok);
    }

    private void createFailJson(HttpServletResponse response, AuthenticationException e) {
        R error = R.error(e.getMessage());
        createJson(response, error);
    }

    private void createFailJson(HttpServletResponse response, String e) {
        R error = R.error(e);
        createJson(response, error);
    }

    private void createJson(HttpServletResponse response, R r) {
        try {
            response.setContentType("application/json;charset=utf-8");
            //如果想修改返回的状态码,可以这么修改
            //response.setStatus(r.getCode());
            //写出去
            PrintWriter writer = response.getWriter();
            writer.write(JSON.toJSONString(r));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

2、简单版

2.1 正常的Controller
复制代码
    @PostMapping("/json")
    public R doJsonLogin(@RequestBody AdminUser adminUser){
        ParameterUtils.checkParameter(adminUser);
        ParameterUtils.checkParameter(adminUser.getUsername(),adminUser.getPassword());
        //import org.springframework.security.core.Authentication;
        Authentication authentication = loginService.doJsonLogin(adminUser);
        return R.ok(authentication);
    }
2.2 正常的Service
复制代码
    @Resource
    HttpSession httpSession;
    @Resource
    HttpServletRequest request;
    @Resource
    HttpServletResponse response;

    @Resource
    SecurityContextRepository securityContextRepository;
    @Override
    public Authentication doJsonLogin(AdminUser adminUser) {
        //获取用户名和密码
        String username = adminUser.getUsername();
        String password = adminUser.getPassword();
        //根据用户名 查询用户信息
        AdminUser loginAdminUser = adminUserService.getByUsername(username);
        if (loginAdminUser == null) {
            throw new JavasmException(ExceptionEnum.User_Not_Found);
        }
        //判断 密码是否正确
        if (!this.passwordEncoder.matches(password, loginAdminUser.getPassword())) {
            throw new JavasmException(ExceptionEnum.Password_Error);
        }
        //登录成功了--以下的代码不能放入多线程中
        //Security已经做了线程安全的设置,如果使用子线程存储数据,是无法获取主线程中的对象的
        UserDetails userDetails = new LoginUserDetails(loginAdminUser);
        //创建一个新的 UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken authenticated =
                UsernamePasswordAuthenticationToken.authenticated(
                        userDetails,
                        loginAdminUser.getPassword(),
                        userDetails.getAuthorities()
                );
        //登录成功的标志,存储到上下文对象中
        //SecurityContextHolder.getContext().setAuthentication(authenticated);
        SecurityContextHolderStrategy strategy = SecurityContextHolder.getContextHolderStrategy();
        strategy.getContext().setAuthentication(authenticated);
        securityContextRepository.saveContext(strategy.getContext(), request, response);
        return authenticated;
    }

七、图片验证码

在json登录的案例中,添加图片验证码

1、引入第三方依赖

复制代码
        <!--图片验证码-->
        <dependency>
            <groupId>com.github.whvcse</groupId>
            <artifactId>easy-captcha</artifactId>
            <version>1.6.2</version>
        </dependency>
        <!--手动引入Nashorn Javascript引擎-->
        <dependency>
            <groupId>org.openjdk.nashorn</groupId>
            <artifactId>nashorn-core</artifactId>
            <version>15.4</version>
        </dependency>

2、生成图片接口

复制代码
    @Resource
    HttpServletResponse response;
    @Resource
    HttpSession httpSession;

    @GetMapping("/imgcode")
    public void createImageCode() throws IOException {
        //ArithmeticCaptcha 算术验证码
        //SpecCaptcha  英文验证码
        // ChineseCaptcha   中文验证码
        // GifCaptcha      动态图片
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(150,50);
        //SpecCaptcha captcha = new SpecCaptcha(150,50);
        //ChineseCaptcha captcha = new ChineseCaptcha(150,50);
        //GifCaptcha captcha = new GifCaptcha(150,50);
        //几个数字的算术题
        captcha.setLen(2);
        //获取验证码的结果--算术题的答案
        String code = captcha.text();
        //验证码的内存,存储到Session对象中,等待调用
        httpSession.setAttribute("img_code",code);
        //输出图片
        captcha.out(response.getOutputStream());
    }

3、修改原有登录代码

复制代码
public class JsonFilter extends UsernamePasswordAuthenticationFilter {
    public static final String SPRING_SECURITY_JSON_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_JSON_PASSWORD_KEY = "password";

    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else if (!request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
            throw new AuthenticationServiceException("请求的类型不对,应该是JSON格式");
        } else {
            //request对象中,可以获取用户名和密码
            ServletInputStream inputStream = request.getInputStream();
            //字节流转成字符流
            InputStreamReader streamReader = new InputStreamReader(inputStream);
            //转成字符流
            BufferedReader bufferedReader = new BufferedReader(streamReader);
            //存储字符的StringBuffer
            StringBuffer jsonBuffer = new StringBuffer();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                jsonBuffer.append(line);
            }
            //用户传入的json字符串
            String json = jsonBuffer.toString().trim();
            if (StringUtils.isEmpty(json)) {
                throw new AuthenticationServiceException("参数为空");
            }
            //把数据转成map
            Map<String, String> map = JSONObject.parseObject(json, Map.class);
            String code = map.get("code");
            Object imgCode = request.getSession().getAttribute("img_code");
            if (code.isEmpty() || imgCode == null ||
                    imgCode.toString().isEmpty() || !code.equals(imgCode)) {
                throw new AuthenticationServiceException("验证码错误");
            }

            String username = map.get(SPRING_SECURITY_JSON_USERNAME_KEY);
            username = username != null ? username.trim() : "";
            String password = map.get(SPRING_SECURITY_JSON_PASSWORD_KEY);
            password = password != null ? password : "";
            UsernamePasswordAuthenticationToken authRequest =
                    UsernamePasswordAuthenticationToken.unauthenticated(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
}
  • 简单版
复制代码
@Resource
    HttpSession httpSession;
    @Resource
    HttpServletRequest request;
    @Resource
    HttpServletResponse response;

    @Resource
    SecurityContextRepository securityContextRepository;
    @Override
    public Authentication doJsonLogin(AdminUser adminUser) {
        String code = adminUser.getCode();
        Object imgCode = httpSession.getAttribute("img_code");
        if (code.isEmpty() || imgCode == null ||
                imgCode.toString().isEmpty() || !code.equals(imgCode)) {
            throw new JavasmException(ExceptionEnum.Code_Error);
        }
        //获取用户名和密码
        String username = adminUser.getUsername();
        String password = adminUser.getPassword();
        //根据用户名 查询用户信息
        AdminUser loginAdminUser = adminUserService.getByUsername(username);
        if (loginAdminUser == null) {
            throw new JavasmException(ExceptionEnum.User_Not_Found);
        }
        //判断 密码是否正确
        if (!this.passwordEncoder.matches(password, loginAdminUser.getPassword())) {
            throw new JavasmException(ExceptionEnum.Password_Error);
        }
        //登录成功了--以下的代码不能放入多线程中
        //Security已经做了线程安全的设置,如果使用子线程存储数据,是无法获取主线程中的对象的
        UserDetails userDetails = new LoginUserDetails(loginAdminUser);
        //创建一个新的 UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken authenticated =
                UsernamePasswordAuthenticationToken.authenticated(
                        userDetails,
                        loginAdminUser.getPassword(),
                        userDetails.getAuthorities()
                );
        //登录成功的标志,存储到上下文对象中
        //SecurityContextHolder.getContext().setAuthentication(authenticated);
        SecurityContextHolderStrategy strategy = SecurityContextHolder.getContextHolderStrategy();
        strategy.getContext().setAuthentication(authenticated);
        securityContextRepository.saveContext(strategy.getContext(), request, response);
        return authenticated;
    }
复制代码
    @TableField(exist = false)
    private String code;

八、手机号验证码

1、发送验证码

复制代码
@GetMapping("/phone/code")
public R sendPhoneCode(String phone){
    String code = loginService.sendPhoneCode(phone);
    return R.ok(code);
}
复制代码
    @Resource
    RedisTemplate<String,Object> redisTemplate;

    @Override
    public String sendPhoneCode(String phone) {
        //生成一个随机数
        String code = RandomUtil.getCode(4);
        //TODO:发送到手机
        //值存入Redis
        String key = String.format(RedisKeys.AdminUserPhone,phone);
        redisTemplate.opsForValue().set(key,code,10, TimeUnit.MINUTES);
        return code;
    }

2、模仿用户名密码登录,写一套手机号验证码登录

  • Filter

  • Authentication

  • Provider

  • UserDetailsService

  • 修改配置文件

复制代码
/phoneLogin
复制代码
{
    "phone":"11111",
    "code":"1234"
}

3、PhoneFilter

用来接收参数

复制代码
public class PhoneFilter extends AbstractAuthenticationProcessingFilter {

    public static final String SPRING_SECURITY_PHONE_KEY = "phone";
    public static final String SPRING_SECURITY_CODE_KEY = "code";
    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER =
            new AntPathRequestMatcher("/phoneLogin", "POST");
    public PhoneFilter() {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
    }

    public PhoneFilter(AuthenticationManager authenticationManager) {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }else if (!request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
            throw new AuthenticationServiceException("请求的类型不对,应该是JSON格式");
        }  else {
            //获取json参数
            Map<String, String> map = RequestJsonUtil.getRequestJson(request);

            String phone = map.get(SPRING_SECURITY_PHONE_KEY);
            phone = phone != null ? phone.trim() : "";
            String code = map.get(SPRING_SECURITY_CODE_KEY);
            code = code != null ? code : "";


            PhoneAuthenticationToken authRequest =
                    PhoneAuthenticationToken.unauthenticated(phone,code);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    protected void setDetails(HttpServletRequest request, PhoneAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }
}

4、PhoneAuthenticationToken

复制代码
public class PhoneAuthenticationToken extends AbstractAuthenticationToken {

    @Getter
    private String phone;
    @Getter
    private String code;
    private UserDetails userDetails;

    //未登录成功之前,调用的
    public PhoneAuthenticationToken(String phone, String code) {
        super((Collection)null);
        this.phone = phone;
        this.code = code;
        this.setAuthenticated(false);
    }
    //登录成功之后,用来向Filter 回值的
    public PhoneAuthenticationToken(UserDetails userDetails) {
        super(userDetails.getAuthorities());
        this.userDetails = userDetails;
        this.code = userDetails.getPassword();
        //保证这里
        super.setAuthenticated(true);
    }
    public static PhoneAuthenticationToken unauthenticated(String phone, String code) {
        return new PhoneAuthenticationToken(phone, code);
    }

    public static PhoneAuthenticationToken authenticated(UserDetails userDetails) {
        return new PhoneAuthenticationToken(userDetails);
    }
    @Override
    public Object getCredentials() {
        return this.code;
    }

    @Override
    public Object getPrincipal() {
        return this.userDetails;
    }

}

5、PhoneProvider

复制代码
@Component
public class PhoneProvider implements AuthenticationProvider {

    @Resource
    RedisTemplate<String,String> redisTemplate;
    @Resource(name = "phoneLoginUserDetailsService")
    UserDetailsService userDetailsService;

    //Authentication  → PhoneAuthenticationToken
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        PhoneAuthenticationToken phoneAuthenticationToken =
                (PhoneAuthenticationToken) authentication;
        //获取手机号
        String phone = phoneAuthenticationToken.getPhone();
        //获取验证码
        String code = phoneAuthenticationToken.getCode();
        //校验 验证码是否正确
        String key = String.format(RedisKeys.AdminUserPhone,phone);
        //从Redis中获取正确的验证码
        String realCode = redisTemplate.opsForValue().get(key);
        if (realCode == null){
            throw new BadCredentialsException("验证码过期");
        }
        //判断用户传入的验证码 是否正确
        if (!realCode.equals(code)){
            throw new BadCredentialsException("验证码错误");
        }
        //删除验证码
        redisTemplate.delete(key);
        //到这里 说明验证码正确---根据手机号,查询UserDetails
        UserDetails userDetails = userDetailsService.loadUserByUsername(phone);
        //初始化 Authentication 设置为 已经登录
        PhoneAuthenticationToken authRequest =
                PhoneAuthenticationToken.authenticated(userDetails);
        //Details
        authRequest.setDetails(phoneAuthenticationToken.getDetails());
        return authRequest;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        //判断 传入的Class类,是不是当前Provider要处理的Authentication
        return PhoneAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

6、UserDetailsService

复制代码
@Service("phoneLoginUserDetailsService")
public class PhoneLoginUserDetailsServiceImpl implements UserDetailsService {

    @Resource
    AdminUserService adminUserService;

    @Override
    public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {
        AdminUser adminUser = adminUserService.getByPhone(phone);
        if (adminUser != null){
            return new LoginUserDetails(adminUser);
        }
        return null;
    }
}

7、修改配置文件

复制代码
import com.alibaba.fastjson2.JSON;
import com.javasm.securitydemo.common.exception.R;
import com.javasm.securitydemo.login.json.JsonFilter;
import com.javasm.securitydemo.login.json.PhoneProvider;
import com.javasm.securitydemo.login.phone.PhoneFilter;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    //****************JSON登录--开始****************//
    @Resource(name = "usernameLoginUserDetailsService")
    UserDetailsService userDetailsService;

    @Bean
    public DaoAuthenticationProvider jsonProvider() {
        DaoAuthenticationProvider authenticationProvider =
                new DaoAuthenticationProvider();
        //配置  由哪个UserDetailsService负责查询用户信息UserDetails
        authenticationProvider.setUserDetailsService(userDetailsService);
        //配置密码的加密方式
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }

    //JSON等于专属的AuthenticationManager
    @Bean("jsonAuthManager")
    public AuthenticationManager jsonAuthenticationManager() {
        return new ProviderManager(List.of(jsonProvider()));
    }

    //配置JsonFilter
    public JsonFilter jsonFilter() {
        JsonFilter jsonFilter = new JsonFilter();
        //配置 AuthenticationManager
        jsonFilter.setAuthenticationManager(jsonAuthenticationManager());
        //json登录成功之后的显示
        jsonFilter.setAuthenticationSuccessHandler(this::createSuccessJson);
        //json登录失败之后,
        jsonFilter.setAuthenticationFailureHandler(
                (r, response, e) ->
                        createFailJson(response, e)
        );
        //配置json登录的路径
        jsonFilter.setFilterProcessesUrl("/jsonlogin");
        return jsonFilter;
    }
    //****************JSON登录--结束****************//


    //****************Phone登录--开始****************//
    @Resource
    PhoneProvider phoneProvider;

    //手机专属的 AuthenticationManger
    @Bean("phoneAuthManager")
    public AuthenticationManager phoneAuthManager() {
        return new ProviderManager(List.of(phoneProvider));
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(phoneProvider);
    }

    @Bean
    public PhoneFilter phoneFilter() {
        PhoneFilter phoneFilter = new PhoneFilter();
        //配置ProviderManager
        phoneFilter.setAuthenticationManager(phoneAuthManager());
        //json登录成功之后的显示
        phoneFilter.setAuthenticationSuccessHandler(this::createSuccessJson);
        //json登录失败之后,
        phoneFilter.setAuthenticationFailureHandler(
                (r, response, e) ->
                        createFailJson(response, e)
        );
        return phoneFilter;
    }

    //****************Phone登录--结束****************//

    @Bean
    public SecurityContextRepository securityContextRepository() {
        return new HttpSessionSecurityContextRepository();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
       
        http.securityContext(context -> context.securityContextRepository(securityContextRepository()));

        //因为当前json登录功能,和用户名密码登录功能类似,
        // 所以把jsonfilter放到UsernamePasswordAuthenticaitonFilter相同的位置
        http.addFilterAt(jsonFilter(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterAt(phoneFilter(), UsernamePasswordAuthenticationFilter.class);
        //关闭csrf 跨域请求伪造的控制
        http.csrf(csrf -> csrf.disable());
        http.authorizeHttpRequests(
                auth ->
                        auth.requestMatchers("/loginpage.html", "/login/**", "/jsonlogin", "/phoneLogin")
                                .permitAll()
                                .anyRequest().authenticated()  //其他页面,要登录之后才能访问
        );//放过登录接口,以及静态页面
        //  ↓配置表单提交
        http.formLogin(form -> {
            // 确保认证信息被正确设置并保存到session中
            form.loginPage("/loginpage.html")        //自定义登录页面的路径
                    .loginProcessingUrl("/javasmlogin")     //表单提交的路径
                    .usernameParameter("uname")              //自定义用户名的参数名(默认是username)
                    .passwordParameter("pwd")
                    //Authentication 是 UsernamePasswordAuthenticationToken-- principal实际的值,UserDetails
                    .successHandler(this::createSuccessJson)
                    //AuthenticationException 包含了 登录失败之后的 异常信息
                    .failureHandler((request, response, exception) ->
                            createFailJson(response, exception)
                    )
                    .permitAll();     //以上提到的路径,都放行
        }).authenticationManager(jsonAuthenticationManager());
        //注销登录
        http.logout(logout -> logout
                .logoutUrl("/logout")
                //退出登录的时候,返回用户信息
                .logoutSuccessHandler((r, response, a) -> createSuccessJson(response, "退出登录成功!"))
                .permitAll()
        );
        //未登录异常提示
        http.exceptionHandling().authenticationEntryPoint((request, response, e) ->
                createFailJson(response, "当前用户未登录,请先登录再访问"));
        return http.build();

    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        //指定加密算法
        return new BCryptPasswordEncoder();
    }

    private void createSuccessJson(HttpServletRequest request,HttpServletResponse response, Authentication authentication) {
        SecurityContextHolderStrategy strategy = SecurityContextHolder.getContextHolderStrategy();
        strategy.getContext().setAuthentication(authentication);
        securityContextRepository().saveContext(strategy.getContext(), request, response);
        createSuccessJson(response,authentication);
    }
    private void createSuccessJson(HttpServletResponse response, Object object) {
        R ok = R.ok(object);
        createJson(response, ok);
    }

    private void createFailJson(HttpServletResponse response, AuthenticationException e) {
        R error = R.error(e.getMessage());
        createJson(response, error);
    }

    private void createFailJson(HttpServletResponse response, String e) {
        R error = R.error(e);
        createJson(response, error);
    }

    private void createJson(HttpServletResponse response, R r) {
        try {
            response.setContentType("application/json;charset=utf-8");
            //如果想修改返回的状态码,可以这么修改
            //response.setStatus(r.getCode());
            //写出去
            PrintWriter writer = response.getWriter();
            writer.write(JSON.toJSONString(r));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

九、跨域

添加一个跨域的过滤器

放到Security过滤器链中

必须在登录的过滤器前面

以前的跨域方式就失效了

  • 配置SpringMVC的Cors映射
复制代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")//允许所有的 路径
                .allowedOriginPatterns("*")
                .allowedMethods("GET","POST","PUT","DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);//预检测请求缓冲时间
    }
}
  • 配置SpringSecurity的跨域支持
复制代码
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOriginPattern("*");//允许所有的域名
        corsConfiguration.addAllowedMethod("*");//允许所有的请求头
        corsConfiguration.setAllowCredentials(true);//允许携带Cookie
        corsConfiguration.setMaxAge(3600L);//预检测请求缓冲时间
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        //对所有的路径应用配置
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }
复制代码
        http.cors(cors -> cors.configurationSource(corsConfigurationSource()));

十、修复bug

表单提交登录,在写了JSON登录之后,就会失效。

找不到

复制代码
{
    "code": 500,
    "msg": "No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
    "timestamp": "2025-12-10T15:46:27.179768"
}

因为缺少了AuthenticationManager,找不到处理UsernamePasswordAuthenticationToken的Provider对象

修改配置,添加Form表单提交需要的AuthenticationManager

复制代码
        http.formLogin(form -> {
            form.loginPage("/loginpage.html")        //自定义登录页面的路径
                    .loginProcessingUrl("/javasmlogin")     //表单提交的路径
                    .usernameParameter("uname")              //自定义用户名的参数名(默认是username)
                    .passwordParameter("pwd")
                    //Authentication 是 UsernamePasswordAuthenticationToken-- principal实际的值,UserDetails
                    .successHandler((request, response, authentication) ->
                            createSuccessJson(response, authentication)
                    )
                    //AuthenticationException 包含了 登录失败之后的 异常信息
                    .failureHandler((request, response, exception) ->
                            createFailJson(response, exception)
                    )
                    .permitAll();     //以上提到的路径,都放行
        }).authenticationManager(jsonAuthenticationManager());

1、完整的SecurityConfig

复制代码
package com.javasm.securitydemo.common.config;
​
import com.alibaba.fastjson2.JSON;
import com.javasm.securitydemo.common.exception.R;
import com.javasm.securitydemo.login.json.JsonFilter;
import com.javasm.securitydemo.login.json.PhoneProvider;
import com.javasm.securitydemo.login.phone.PhoneFilter;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
​
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
​
@Configuration
@EnableWebSecurity
public class SecurityConfig {
​
    //****************JSON登录--开始****************//
    @Resource(name = "usernameLoginUserDetailsService")
    UserDetailsService userDetailsService;
​
    @Bean
    public DaoAuthenticationProvider jsonProvider() {
        DaoAuthenticationProvider authenticationProvider =
                new DaoAuthenticationProvider();
        //配置  由哪个UserDetailsService负责查询用户信息UserDetails
        authenticationProvider.setUserDetailsService(userDetailsService);
        //配置密码的加密方式
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }
​
    //JSON等于专属的AuthenticationManager
    @Bean("jsonAuthManager")
    public AuthenticationManager jsonAuthenticationManager() {
        return new ProviderManager(List.of(jsonProvider()));
    }
​
    //配置JsonFilter
    public JsonFilter jsonFilter() {
        JsonFilter jsonFilter = new JsonFilter();
        //配置 AuthenticationManager
        jsonFilter.setAuthenticationManager(jsonAuthenticationManager());
        //json登录成功之后的显示
        jsonFilter.setAuthenticationSuccessHandler(this::createSuccessJson);
        //json登录失败之后,
        jsonFilter.setAuthenticationFailureHandler(
                (r, response, e) ->
                        createFailJson(response, e)
        );
        //配置json登录的路径
        jsonFilter.setFilterProcessesUrl("/jsonlogin");
        return jsonFilter;
    }
    //****************JSON登录--结束****************//
​
​
    //****************Phone登录--开始****************//
    @Resource
    PhoneProvider phoneProvider;
​
    //手机专属的 AuthenticationManger
    @Bean("phoneAuthManager")
    public AuthenticationManager phoneAuthManager() {
        return new ProviderManager(List.of(phoneProvider));
    }
​
    @Autowired
    public void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(phoneProvider);
    }
​
    @Bean
    public PhoneFilter phoneFilter() {
        PhoneFilter phoneFilter = new PhoneFilter();
        //配置ProviderManager
        phoneFilter.setAuthenticationManager(phoneAuthManager());
        //json登录成功之后的显示
        phoneFilter.setAuthenticationSuccessHandler(this::createSuccessJson);
        //json登录失败之后,
        phoneFilter.setAuthenticationFailureHandler(
                (r, response, e) ->
                        createFailJson(response, e)
        );
        return phoneFilter;
    }
​
    //****************Phone登录--结束****************//
​
    //****************跨域--开始****************//
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOriginPattern("*");//允许所有的域名
        corsConfiguration.addAllowedMethod("*");//允许所有的请求头
        corsConfiguration.setAllowCredentials(true);//允许携带Cookie
        corsConfiguration.setMaxAge(3600L);//预检测请求缓冲时间
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        //对所有的路径应用配置
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }
    //****************跨域--结束****************//
    
    @Bean
    public SecurityContextRepository securityContextRepository() {
        return new HttpSessionSecurityContextRepository();
    }
​
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
        http.securityContext(context -> context.securityContextRepository(securityContextRepository()));
​
        //因为当前json登录功能,和用户名密码登录功能类似,
        // 所以把jsonfilter放到UsernamePasswordAuthenticaitonFilter相同的位置
        http.addFilterAt(jsonFilter(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterAt(phoneFilter(), UsernamePasswordAuthenticationFilter.class);
        //关闭csrf 跨域请求伪造的控制
        http.csrf(csrf -> csrf.disable());
        http.authorizeHttpRequests(
                auth ->
                        auth.requestMatchers("/loginpage.html", "/login/**", "/jsonlogin", "/phoneLogin")
                                .permitAll()
                                .anyRequest().authenticated()  //其他页面,要登录之后才能访问
        );//放过登录接口,以及静态页面
        //  ↓配置表单提交
        http.formLogin(form -> {
            // 确保认证信息被正确设置并保存到session中
            form.loginPage("/loginpage.html")        //自定义登录页面的路径
                    .loginProcessingUrl("/javasmlogin")     //表单提交的路径
                    .usernameParameter("uname")              //自定义用户名的参数名(默认是username)
                    .passwordParameter("pwd")
                    //Authentication 是 UsernamePasswordAuthenticationToken-- principal实际的值,UserDetails
                    .successHandler(this::createSuccessJson)
                    //AuthenticationException 包含了 登录失败之后的 异常信息
                    .failureHandler((request, response, exception) ->
                            createFailJson(response, exception)
                    )
                    .permitAll();     //以上提到的路径,都放行
        }).authenticationManager(jsonAuthenticationManager());
        //注销登录
        http.logout(logout -> logout
                .logoutUrl("/logout")
                //退出登录的时候,返回用户信息
                .logoutSuccessHandler((r, response, a) -> createSuccessJson(response, "退出登录成功!"))
                .permitAll()
        );
        //未登录异常提示
        http.exceptionHandling().authenticationEntryPoint((request, response, e) ->
                createFailJson(response, "当前用户未登录,请先登录再访问"));
        return http.build();
​
    }
​
    @Bean
    public PasswordEncoder passwordEncoder() {
        //指定加密算法
        return new BCryptPasswordEncoder();
    }
​
    private void createSuccessJson(HttpServletRequest request,HttpServletResponse response, Authentication authentication) {
        SecurityContextHolderStrategy strategy = SecurityContextHolder.getContextHolderStrategy();
        strategy.getContext().setAuthentication(authentication);
        securityContextRepository().saveContext(strategy.getContext(), request, response);
        createSuccessJson(response,authentication);
    }
    private void createSuccessJson(HttpServletResponse response, Object object) {
        R ok = R.ok(object);
        createJson(response, ok);
    }
​
    private void createFailJson(HttpServletResponse response, AuthenticationException e) {
        R error = R.error(e.getMessage());
        createJson(response, error);
    }
​
    private void createFailJson(HttpServletResponse response, String e) {
        R error = R.error(e);
        createJson(response, error);
    }
​
    private void createJson(HttpServletResponse response, R r) {
        try {
            response.setContentType("application/json;charset=utf-8");
            //如果想修改返回的状态码,可以这么修改
            //response.setStatus(r.getCode());
            //写出去
            PrintWriter writer = response.getWriter();
            writer.write(JSON.toJSONString(r));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

第二天总结

  • 掌握Security的请求流程,能用语言描述

  • 能够获取登录用户信息

  • 修改登录用户信息

  • 其他案例跟着课堂敲一遍,尽可能掌握,不强求

相关推荐
期待のcode2 小时前
springboot热部署
java·spring boot·后端
222you2 小时前
Spring框架的介绍和IoC入门
java·后端·spring
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于Java的人体骨骼健康知识普及系统为例,包含答辩的问题和答案
java·开发语言
喵手2 小时前
集合框架概述:让数据操作更高效、更灵活!
java·集合·集合框架
Java爱好狂.2 小时前
如何用JAVA技术设计一个高并发系统?
java·数据库·高并发·架构设计·java面试·java架构师·java八股文
sheji34162 小时前
【开题答辩全过程】以 基于JAVA的社团管理系统为例,包含答辩的问题和答案
java·开发语言
油丶酸萝卜别吃2 小时前
lombok的几个核心注解是什么?
java·tomcat
毕设源码-邱学长3 小时前
【开题答辩全过程】以 个性化新闻推荐系统为例,包含答辩的问题和答案
java
a程序小傲3 小时前
京东Java面试被问:ZGC的染色指针如何实现?内存屏障如何处理?
java·后端·python·面试