ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

仿牛客网社区开发——第7章 项目进阶,构建安全高效的企业服务

2022-07-10 15:34:20  阅读:192  来源: 互联网

标签:return 进阶 request 认证 客网 Override 仿牛 public String


Spring Security

简介

Spring Security 是一个专注于为 Java 应用程序提供身份认证和授权的框架,它的强大之处在于它可以轻松扩展以满足自定义的需求。

特征

  • 对身份的认证授权提供全面的、可扩展的支持
  • 防止各种攻击,如会话固定攻击、点击劫持、csrf 攻击等
  • 支持与 Servlet API、Spring MVC 等 Web 技术集成

原理

官网:https://spring.io/projects/spring-security

中文学习网址:http://www.spring4all.com/article/428

简要来说,Spring Security 底层是利用的 Java EE 的规范。底层用了很多的 Filter,不同的 Filter 负责不同的功能。

引入 Spring Security 的依赖

引入包之后,Spring Security 权限就会生效 ,会生成随机密码,访问资源时会被拦截。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

改造 User 实体类以及 UserService

修改 User 类

  • 实现 UserDetails 接口
  • 重写几个方法,getAuthorities 返回用户权限,其它方法默认都返回 true
  • 一个用户可能有多个权限,但这里一个用户只需要一个权限即可,所以 getAuthorities 就不需要返回集合
public class User implements UserDetails {

    ……
        
    // true:账号未过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // true:账号未锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // true:凭证未过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // true:账号可用
    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        ArrayList<GrantedAuthority> list = new ArrayList<>();
        list.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                switch (type) {
                    case 1:
                        return "ADMIN";
                    default:
                        return "USER";
                }
            }
        });
        return list;
    }
}

修改 UserService 类

Spring Security 需要依赖 UserDetailsService 接口查询用户,所以需要在 UserService 实现此接口,并且实现其方法,直接调用 UserService 的查询方法

@Service
public class UserService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    public User findUserByName(String username) {
        return userMapper.selectByName(username);
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return findUserByName(username);
    }
}

编写 Security 配置类

  1. 前两个 configure 方法主要是认证的逻辑:
    • 静态资源不需要拦截
    • 不采用内置的认证规则,采用自定义认证规则。因为之前用的不是这个 Encoder,并且也不是这个固定的 salt,所以这个逻辑和我们目前的系统的现状不匹配
    • 自定义认证规则中,每个 AuthenticationProvider 负责一种认证,例如有账号密码认证、QQ 认证、微信认证等等。ProviderManager 持有一组 AuthenticationProvider,ProviderManager 将认证委托给 AuthenticationProvider
    • 账号不存在密码不正确,抛出各自对应的异常
    • 返回时填上 3 个对应的参数
    • supports 方法中返回当前支持的认证类型
  2. 第三个 configure 方法:
    • 进行登录和退出的相关配置:主要是路径成功和失败的处理
    • 授权配置:每个路径能有哪些权限可以访问、拒绝访问的跳转路径
    • 验证码配置:在账号密码的 Filter 之前;如果路径为 /login,且验证码错误,则拦截;否则应当放行,必须写 doFilter
    • 记住我的配置:凭证存放到何处、有效时间,以及配置 userService。这里当下次访问这个网站的时候,它从内存里能根据你的凭证得到你的用户名,然后得查出用户的完整信息。怎么查呢,用这个 userService 来查,所以你得告诉它
  3. 另外注意各个地方是请求转发还是重定向
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 忽略静态资源的访问
        web.ignoring().antMatchers("/resources/**");
    }

    // AuthenticationManager:认证的核心接口
    // AuthenticationManagerBuilder:用于构建AuthenticationManager对象的工具
    // ProviderManager:AuthenticationManager接口的默认实现类
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 内置的认证规则
        //auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345"));

        // 自定义认证规则
        // AuthenticationProvider:ProviderManager持有一组AuthenticationProvider,每个AuthenticationProvider负责一种认证
        // 委托模式:ProviderManager将认证委托给AuthenticationProvider
        auth.authenticationProvider(new AuthenticationProvider() {
            // Authentication:用于封装认证信息的接口,不同的实现类代表不同类型的认证信息
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                String username = authentication.getName();
                String password = (String) authentication.getCredentials();

                User user = userService.findUserByName(username);
                if (user == null) {
                    throw new UsernameNotFoundException("账号不存在!");
                }

                password = CommunityUtil.md5(password + user.getSalt());
                if (!user.getPassword().equals(password)) {
                    throw new BadCredentialsException("密码不正确!");
                }

                // principal:主要信息;credentials:证书;authorities:权限;
                return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
            }

            // 当前的AuthenticationProvider支持哪种类型的认证
            @Override
            public boolean supports(Class<?> aClass) {
                // UsernamePasswordAuthenticationToken:Authentication接口的常用的实现类
                return UsernamePasswordAuthenticationToken.class.equals(aClass);
            }
        });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 登录相关配置
        http.formLogin()
                .loginPage("/loginpage")
                .loginProcessingUrl("/login")
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.sendRedirect(request.getContextPath() + "/index");
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        request.setAttribute("error", e.getMessage());
                        request.getRequestDispatcher("/loginpage").forward(request, response);
                    }
                });

        // 退出相关配置
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.sendRedirect(request.getContextPath() + "/index");
                    }
                });

        // 授权配置
        http.authorizeRequests()
                .antMatchers("/letter").hasAnyAuthority("USER", "ADMIN")
                .antMatchers("/admin").hasAnyAuthority("ADMIN")
                .and().exceptionHandling().accessDeniedPage("/denied");

        // 增加Filter,处理验证码
        http.addFilterBefore(new Filter() {
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                HttpServletRequest request = (HttpServletRequest) servletRequest;
                HttpServletResponse response = (HttpServletResponse) servletResponse;
                if (request.getServletPath().equals("/login")) {
                    String verifyCode = request.getParameter("verifyCode");
                    if (verifyCode == null || !verifyCode.equals("1234")) {
                        request.setAttribute("error", "验证码错误!");
                        request.getRequestDispatcher("/loginpage").forward(request, response);
                        return;
                    }
                }
                // 让请求继续向下执行
                filterChain.doFilter(request, response);
            }
        }, UsernamePasswordAuthenticationFilter.class);

        // 记住我
        http.rememberMe()
                .tokenRepository(new InMemoryTokenRepositoryImpl())
                .tokenValiditySeconds(3600 * 24)
                .userDetailsService(userService);
    }
}

HomeController

尤其注意 /index 方法,如何获取到之前存的用户信息。认证成功后,结果会通过 SecurityContextHolder 存入 SecurityContext 中

@Controller
public class HomeController {

    @RequestMapping(path = "/index", method = RequestMethod.GET)
    public String getIndexPage(Model model) {
        // 认证成功后,结果会通过SecurityContextHolder存入SecurityContext中
        Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (obj instanceof User) {
            model.addAttribute("loginUser", obj);
        }
        return "/index";
    }

    @RequestMapping(path = "/discuss", method = RequestMethod.GET)
    public String getDiscussPage() {
        return "/site/discuss";
    }

    @RequestMapping(path = "/letter", method = RequestMethod.GET)
    public String getLetterPage() {
        return "/site/letter";
    }

    @RequestMapping(path = "/admin", method = RequestMethod.GET)
    public String getAdminPage() {
        return "/site/admin";
    }

    @RequestMapping(path = "/loginpage", method = {RequestMethod.GET, RequestMethod.POST})
    public String getLoginPage() {
        return "/site/login";
    }

    // 拒绝访问时的提示页面
    @RequestMapping(path = "/denied", method = RequestMethod.GET)
    public String getDeniedPage() {
        return "/error/404";
    }
}

前端页面

index.html

Spring Security 要求退出得是 post 请求,这里改成表单,注意超链接如何起提交的效果

<h1>社区首页</h1>
<!--欢迎信息-->
<p th:if="${loginUser!=null}">
    欢迎你,<span th:text="${loginUser.username}"></span>!
</p>

<ul>
    <li><a th:href="@{/discuss}">帖子详情</a></li>
    <li><a th:href="@{/letter}">私信列表</a></li>
    <li><a th:href="@{/loginpage}">登录</a></li>
    <!--<li><a th:href="@{/logout}">退出</a></li>-->
    <li>
        <form method="post" th:action="@{/logout}">
            <a href="javascript:document.forms[0].submit();">退出</a>
        </form>
    </li>
</ul>

login.html

记住我的 name 固定,一定要叫 remember-me

<h1>登录社区</h1>

<form method="post" th:action="@{/login}">
    <p style="color:red;" th:text="${error}">
        <!--提示信息-->
    </p>
    <p>
        账号:<input type="text" name="username" th:value="${param.username}">
    </p>
    <p>
        密码:<input type="password" name="password" th:value="${param.password}">
    </p>
    <p>
        验证码:<input type="text" name="verifyCode"> <i>1234</i>
    </p>
    <p>
        <input type="checkbox" name="remember-me"> 记住我
    </p>
    <p>
        <input type="submit" value="登录">
    </p>
</form>

 

标签:return,进阶,request,认证,客网,Override,仿牛,public,String
来源: https://www.cnblogs.com/CWZhou/p/16463262.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有