A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

下面是基于简单加密 token 的方法的实现,基于前篇的限制登录次数的功能之上加入remember me功能
项目结构如下:

基本的结构没有变化,主要在于一些类的修改和配置。
一、修改SecurityConfig配置文件
[url=][/url]
package com.petter.config;import com.petter.handler.CustomAuthenticationProvider;import com.petter.service.CustomUserDetailsService;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;import javax.annotation.Resource;/** * 相当于spring-security.xml中的配置 * @author hongxf * @since 2017-03-08 9:30 */@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Resource    private CustomAuthenticationProvider authenticationProvider;    @Resource    private CustomUserDetailsService userDetailsService;    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.authenticationProvider(authenticationProvider);    }    /**     * 配置权限要求     * 采用注解方式,默认开启csrf     * @param http     * @throws Exception     */    @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests()                .antMatchers("/admin/**").hasRole("ADMIN")                .antMatchers("/dba/**").hasAnyRole("ADMIN", "DBA")            .and()                .formLogin().successHandler(savedRequestAwareAuthenticationSuccessHandler())                .loginPage("/login") //指定自定义登录页                .failureUrl("/login?error") //登录失败的跳转路径                .loginProcessingUrl("/auth/login_check") //指定了登录的form表单提交的路径,需与表单的action值保存一致,默认是login                .usernameParameter("user-name").passwordParameter("pwd")            .and()                .logout().logoutSuccessUrl("/login?logout")            .and()                .exceptionHandling().accessDeniedPage("/403")            .and()                .csrf()            .and()                .rememberMe().rememberMeParameter("remember-me") //其实默认就是remember-me,这里可以指定更换                .tokenValiditySeconds(1209600)                .key("hongxf");    }    //使用remember-me必须指定UserDetailsService    @Override    protected UserDetailsService userDetailsService() {        return userDetailsService;    }    /**     * 这里是登录成功以后的处理逻辑     * 设置目标地址参数为targetUrl     * /auth/login_check?targetUrl=/admin/update     * 这个地址就会被解析跳转到/admin/update,否则就是默认页面     *     * 本示例中访问update页面时候会判断用户是手动登录还是remember-me登录的     * 如果是remember-me登录的则会跳转到登录页面进行手动登录再跳转     * @return     */    @Bean    public SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler() {        SavedRequestAwareAuthenticationSuccessHandler auth = new SavedRequestAwareAuthenticationSuccessHandler();        auth.setTargetUrlParameter("targetUrl");        return auth;    }}[url=][/url]

这里需要指出几点:


1、使用remember-me功能必须指定UserDetailsService
2、修改登录成功以后的逻辑,具体见注释
3、添加remember me的配置,key("hongxf"),这里的key用于加密,可以进行指定
二、修改admin.html,添加
<div sec:authorize="isRememberMe()">        <h2>该用户是通过remember me cookies登录的</h2>    </div>    <div sec:authorize="isFullyAuthenticated()">        <h2>该用户是通过输入用户名和密码登录的</h2>    </div>
用于展示

三、修改登录页面login.html
form表单需要进行相应的修改
[url=][/url]
<form name='loginForm' th:action="@{/auth/login_check(targetUrl=${session.targetUrl})}" method='POST'>            <table>                <tr>                    <td>用户名:</td>                    <td><input type='text' name='user-name' /></td>                </tr>                <tr>                    <td>密码:</td>                    <td><input type='password' name='pwd' /></td>                </tr>                <!-- 如果是进行更新操作跳转过来的页面则不显示记住我 -->                <div th:if="${loginUpdate} eq null">                    <tr>                        <td></td>                        <td>记住我: <input type="checkbox" name="remember-me" /></td>                    </tr>                </div>                <tr>                    <td colspan='2'>                        <input type="submit" value="提交" />                    </td>                </tr>            </table>        </form>[url=][/url]

注意action的值,首先请求路径是/auth/login_check,与SecurityConfig配置的loginProcessingUrl保持一致

/auth/login_check(targetUrl=${session.targetUrl})会被解析成/auth/login_check?targetUrl=XXX 其中targetUrl的从session中获取

四、编写update.html页面
[url=][/url]
<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>update</title></head><body>    <h1>Title : 更新页面</h1>    <h1>只有通过用户名和密码登录的用户才允许进入这个页面,remember me登录的用户不允许,防止被盗用cookie</h1>    <h2>更新账号信息</h2></body></html>[url=][/url]

五、修改HelloController类
[url=][/url]
package com.petter.web;import org.springframework.security.authentication.AnonymousAuthenticationToken;import org.springframework.security.authentication.BadCredentialsException;import org.springframework.security.authentication.LockedException;import org.springframework.security.authentication.RememberMeAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Controller;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;/** * @author hongxf * @since 2017-03-08 9:29 */@Controllerpublic class HelloController {    @RequestMapping(value = { "/", "/welcome" }, method = RequestMethod.GET)    public ModelAndView welcomePage() {        ModelAndView model = new ModelAndView();        model.addObject("title", "Spring Security Hello World");        model.addObject("message", "This is welcome page!");        model.setViewName("hello");        return model;    }    @RequestMapping(value = "/admin", method = RequestMethod.GET)    public ModelAndView adminPage() {        ModelAndView model = new ModelAndView();        model.addObject("title", "Spring Security Hello World");        model.addObject("message", "This is protected page - Admin Page!");        model.setViewName("admin");        return model;    }    @RequestMapping(value = "/dba", method = RequestMethod.GET)    public ModelAndView dbaPage() {        ModelAndView model = new ModelAndView();        model.addObject("title", "Spring Security Hello World");        model.addObject("message", "This is protected page - Database Page!");        model.setViewName("admin");        return model;    }    /**     * 登录页面只允许使用密码登录     * 如果用户通过remember me的cookie登录则跳转到登录页面输入密码     * 为了避免盗用remember me cookie 来更新信息     */    @RequestMapping(value = "/admin/update", method = RequestMethod.GET)    public ModelAndView updatePage(HttpServletRequest request) {        ModelAndView model = new ModelAndView();        if (isRememberMeAuthenticated()) {            //把targetUrl放入session中,登录页面使用${session.targetUrl}获取            setRememberMeTargetUrlToSession(request);            //跳转到登录页面            model.addObject("loginUpdate", true);            model.setViewName("login");        } else {            model.setViewName("update");        }        return model;    }    /**     * 判断用户是不是通过remember me方式登录,参考     * org.springframework.security.authentication.AuthenticationTrustResolverImpl     */    private boolean isRememberMeAuthenticated() {        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();        return authentication != null && RememberMeAuthenticationToken.class.isAssignableFrom(authentication.getClass());    }    /**     * 保存请求的页面targetUrl到session中     */    private void setRememberMeTargetUrlToSession(HttpServletRequest request){        HttpSession session = request.getSession(false);        if(session != null){            session.setAttribute("targetUrl", request.getRequestURI());        }    }    //获取session存储的SPRING_SECURITY_LAST_EXCEPTION的值,自定义错误信息    @RequestMapping(value = "/login", method = RequestMethod.GET)    public ModelAndView login(            @RequestParam(value = "error", required = false) String error,            @RequestParam(value = "logout", required = false) String logout,            HttpServletRequest request) {        ModelAndView model = new ModelAndView();        if (error != null) {            model.addObject("error", getErrorMessage(request, "SPRING_SECURITY_LAST_EXCEPTION"));            //在update的登录页面上,判断targetUrl是否有值,没有则显示记住我,有则不显示            String targetUrl = getRememberMeTargetUrlFromSession(request);            System.out.println(targetUrl);            if(StringUtils.hasText(targetUrl)){                model.addObject("loginUpdate", true);            }        }        if (logout != null) {            model.addObject("msg", "你已经成功退出");        }        model.setViewName("login");        return model;    }    /**     * 从session中获取targetUrl     */    private String getRememberMeTargetUrlFromSession(HttpServletRequest request){        String targetUrl = "";        HttpSession session = request.getSession(false);        if(session != null){            targetUrl = session.getAttribute("targetUrl") == null ? "" :session.getAttribute("targetUrl").toString();        }        return targetUrl;    }    //自定义错误类型    private String getErrorMessage(HttpServletRequest request, String key){        Exception exception = (Exception) request.getSession().getAttribute(key);        String error;        if (exception instanceof BadCredentialsException) {            error = "不正确的用户名或密码";        }else if(exception instanceof LockedException) {            error = exception.getMessage();        }else{            error = "不正确的用户名或密码";        }        return error;    }    @RequestMapping(value = "/403", method = RequestMethod.GET)    public ModelAndView accessDenied() {        ModelAndView model = new ModelAndView();        //检查用户是否已经登录        Authentication auth = SecurityContextHolder.getContext().getAuthentication();        if (!(auth instanceof AnonymousAuthenticationToken)) {            UserDetails userDetail = (UserDetails) auth.getPrincipal();            model.addObject("username", userDetail.getUsername());        }        model.setViewName("403");        return model;    }}[url=][/url]

六、进行测试

启动应用,访问http://localhost:8080/admin 会跳转到登录页
输入正确的用户名和密码,勾选记住我,登录成功进入admin页面





可以查看此时的cookie中的值




可以看到remember-me的值以及失效日期

想要尝试记住我免登录功能,重启应用,访问http://localhost:8080/admin ,可以看到



现在尝试访问http://localhost:8080/admin/update  则会跳转到登录页面


注意上面的访问地址就是http://localhost:8080/admin/update 只是返回的是登录页内容,并且隐藏了记住我选项,查看页面源代码,可以看到

<form name='loginForm' action="/auth/login_check?targetUrl=/admin/update" method='POST'>
当输入争取的用户名和密码时候security会根据targetUrl的值跳转到之前访问的页面





0 个回复

您需要登录后才可以回帖 登录 | 加入黑马