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 @EnableWebSecurity public 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; } }
這裏須要指出幾點:html
<div sec:authorize="isRememberMe()"> <h2>該用戶是經過remember me cookies登陸的</h2> </div> <div sec:authorize="isFullyAuthenticated()"> <h2>該用戶是經過輸入用戶名和密碼登陸的</h2> </div>
用於展現java
<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>
注意action的值,首先請求路徑是/auth/login_check,與SecurityConfig配置的loginProcessingUrl保持一致web
<!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>
5、修改HelloController類spring
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 */ @Controller public 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; } }
6、進行測試數據庫