1. 程式人生 > >springboot 整合spring security

springboot 整合spring security

轉載務必說明出處:https://blog.csdn.net/LiaoHongHB/article/details/83576911

       spring security 是一種安全框架,主要的作用是認證和授權;其中認證則是判斷該使用者是誰,授權則是判斷該使用者有無許可權訪問該url。一個專案一般情況分為前臺的業務系統(電商網站)和後臺管理系統;其中前臺的業務系統主要操作物件為普通使用者,角色相對少,許可權也比較固定,所以在前臺的業務系統中一般不採用spring security;後臺管理系統則主要是由內部的管理員對前臺的業務系統進行相關操作,涉及到的角色較多,許可權也比較複雜,所以我們一般在後臺管理系統中採用spring security。

       接下來就通過spring boot整合一個基本的spring security。

      1、首先匯入jar包

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

     2、建立一個SecurityConfig檔案,繼承WebSecurityConfigureAdapter,重寫其中的一個config方法

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenticationFailureHander myAuthenticationFailureHander;

    @Autowired
    private SecurityProperites securityProperites;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                //跳轉的登陸頁面action
                .loginPage("/login")                
                //登陸頁面的action
                .loginProcessingUrl("/authentication/login") 
                //登陸失敗的頁面url
                .failureUrl("/login-error.jsp")
                .permitAll()
                //登陸成功的處理器
                .successHandler(myAuthenticationSuccessHandler)
                //登陸失敗的處理器
                .failureHandler(myAuthenticationFailureHander)
                .and()
                .authorizeRequests()
                //表示antMatchers括號中的url不需要許可權就可以訪問
                .antMatchers("/login",
                        "/login-error.jsp",
                        "/authentication/login",
                        "/demo-signIn.jsp",
                      
                securityProperites.getBrowserProperites().getLoginPage()).permitAll()
                //表示除了上述的url之外,其他的url訪問都需要許可權認證
                .anyRequest()
                //使用rbac框架來做許可權認證處理
                .access("@rbacService.hasPermission(request,authentication)")
                .and()
                .csrf().disable();
    }
3、建立User類,實現序列化介面和UserDetail物件
public class User implements Serializable,UserDetails {

    private static final long serialVersionUID = -2279654576873280156L;
    private String username;
    private String password;
    private String role;

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}
4、新建LoginController並寫一個login方法
@RestController
public class LoginController {

    private Logger logger = LoggerFactory.getLogger(LoginController.class);

    private RequestCache requestCache = new HttpSessionRequestCache();
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Autowired
    private SecurityProperites securityProperites;

    @RequestMapping("/login")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public RestFulVO login(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if (savedRequest != null) {
            // 獲取引發授權跳轉的請求地址
            String targetUrl = savedRequest.getRedirectUrl();
            logger.info("此次訪問【" + targetUrl + "】沒有授權,需要跳轉到授權頁面");
            if (StringUtils.endsWithIgnoreCase(targetUrl,".jsp")) {
                redirectStrategy.sendRedirect(request,response,securityProperites.getBrowserProperites().getLoginPage());
            }
        }
        return new RestFulVO("訪問的服務需要身份認證");
    }

如果發起的請求是以.jsp結尾的,則返回到登陸頁面;否則直接返回一個json型別的資料“訪問的服務需要身份認證”

5、新建UserService物件,繼承UserDetailService,重寫其中的loadUserByUsername方法,判斷使用者輸入的username和  password是否正確

public interface UserService extends UserDetailsService {

    @Override
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}



@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private PasswordEncoder passwordEncoder;

//    @Autowired
//    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        /*
        在實際開發過程中這裡本來是應該通過呼叫userMapper中的方法判斷使用者輸入的username和password和資料庫中的資料是否一致
        但是為了說明方便,直接new User物件
         */
        System.out.println("username:  " + username);
        String password = passwordEncoder.encode("123456");
        System.out.println("password:   " + password);
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        user.getAuthorities();
        return user;
//        return new org.springframework.security.core.userdetails.User(username,password,AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin"));
    }
}
6、建立MyAuthenticationSuccessHandler,自定義登陸成功的處理器
@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private Logger logger = LoggerFactory.getLogger(MyAuthenticationSuccessHandler.class);

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperites securityProperites;

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

        logger.info("登陸成功......");
        //這裡可以根據實際情況,來確定是跳轉到頁面或者json格式。
        if (LoginType.JSON.equals(securityProperites.getBrowserProperites().getLoginType())) {
            //如果是返回json格式,那麼我們這麼寫
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        } else {
            super.onAuthenticationSuccess(request,response,authentication);
        }
    }
}

7、建立MyAuthenticationFailureHandler,自定義登陸失敗的處理器

@Component("myAuthenticationFailureHander")
public class MyAuthenticationFailureHander extends SimpleUrlAuthenticationFailureHandler {

    private Logger logger = LoggerFactory.getLogger(MyAuthenticationFailureHander.class);

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperites securityProperites;

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

        logger.info("登陸失敗");
        if (LoginType.JSON.equals(securityProperites.getBrowserProperites().getLoginType())) {
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(exception));
        } else {
            super.onAuthenticationFailure(request,response,exception);
        }
    }
}

8、建立RbacService介面,定義一個介面方法,然後建立RbacServiceImpl實現該介面並重寫介面方法,以達到對使用者進行許可權認證的功能

public interface RbacService {
    boolean hasPermission(HttpServletRequest request, Authentication authentication);
}


@Component("rbacService")
public class RbacServiceImpl implements RbacService {

    private static Logger logger = LoggerFactory.getLogger(RbacServiceImpl.class);


    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        Object principal = authentication.getPrincipal();
        boolean hasPermission = false;
        if (principal instanceof UserDetails) { //首先判斷先當前使用者是否是我們UserDetails物件。
            Set<String> urls = new HashSet<>(); // 資料庫讀取 //讀取使用者所擁有許可權的所有URL
            // 注意這裡不能用equal來判斷,因為有些URL是有引數的,所以要用AntPathMatcher來比較
            System.out.println(request.getRequestURI());
            for (String url : urls) {
                if (antPathMatcher.match(url, request.getRequestURI())) {
                    hasPermission = true;
                    break;
                }
            }
            if (!hasPermission) {
                logger.warn("該使用者{}沒有{}訪問許可權", principal, request.getRequestURI());
            }
        }
        return hasPermission;
    }
}

在hasPermission方法實現中,應該根據該使用者的id來查詢該使用者所有許可權的url集合,然後通過match方法將該使用者現在訪問的url地址(request.getRequestURI)與url集合進行匹配,如果匹配成功,則說明該使用者又許可權訪問該url,返回true;否則返回false;在實際開發過程中,這裡也應該使用Mapper根據userId查詢資料庫獲取該userId下的所有可訪問的url集合。

9、啟動測試

啟動該專案,並在位址列輸入localhost:5001/index.jsp

由於index.jsp不在antMatches中,所以首先會在RbacServiceImpl中判斷該使用者是否有許可權訪問

 .authorizeRequests()
                //表示antMatchers括號中的url不需要許可權就可以訪問
                .antMatchers("/login",
                        "/login-error.jsp",
                        "/authentication/login",
                        "/demo-signIn.jsp",
                        securityProperites.getBrowserProperites().getLoginPage()).permitAll()
                //表示除了上述的url之外,其他的url訪問都需要許可權認證
                .anyRequest()

         如果返回true,這表示該使用者擁有訪問index.jsp的許可權;如果返回false則表示該沒有沒有許可權訪問index.jsp,接下來跳到LoginAction中login方法進行判斷,由於訪問的index.jsp是以.jsp結尾的,也就是說是頁面,則再跳轉到授權頁面,也就是登陸頁面,使用者在輸入使用者名稱和密碼之後,點選登陸,由於登陸的action為“/authentication/login”不需要許可權,所以直接跳轉到UserServiceImpl中驗證使用者資訊是否正確,如果登陸失敗則通過自定義的FailureHandle返回相關資訊,如果登陸成功,則跳轉到RbacServiceImpl中重新驗證該使用者有無許可權,如果true則顯示index.jsp相關資訊,如果false則顯示該使用者沒有許可權(access deny)。

    沒有許可權的頁面: