Spring Boot 集成 Spring Security 實現權限認證模塊

做者:王帥@CodeSheepmysql

 

 

 

寫在前面

關於 Spring Security web

Web系統的認證和權限模塊也算是一個系統的基礎設施了,幾乎任何的互聯網服務都會涉及到這方面的要求。在Java EE領域,成熟的安全框架解決方案通常有 Apache Shiro、Spring Security等兩種技術選型。Apache Shiro簡單易用也算是一大優點,但其功能仍是遠不如 Spring Security強大。Spring Security能夠爲 Spring 應用提供聲明式的安全訪問控制,起經過提供一系列能夠在 Spring應用上下文中可配置的Bean,並利用 Spring IoC和 AOP等功能特性來爲應用系統提供聲明式的安全訪問控制功能,減小了諸多重複工做。spring

關於JWT sql

JSON Web Token (JWT),是在網絡應用間傳遞信息的一種基於 JSON的開放標準((RFC 7519),用於做爲JSON對象在不一樣系統之間進行安全地信息傳輸。主要使用場景通常是用來在 身份提供者和服務提供者間傳遞被認證的用戶身份信息。關於JWT的科普,能夠看看阮一峯老師的《JSON Web Token 入門教程》。數據庫

本文則結合 Spring Security和 JWT兩大利器來打造一個簡易的權限系統。json

本文實驗環境以下:安全

  • Spring Boot版本: 2.0.6.RELEASE網絡

  • IDE: IntelliJIDEA2018.2.4session

另外本文實驗代碼置於文尾,須要自取。框架


設計用戶和角色

本文實驗爲了簡化考慮,準備作以下設計:

  • 設計一個最簡角色表 role,包括 角色ID和 角色名稱

 

 

  • 設計一個最簡用戶表 user,包括 用戶ID, 用戶名, 密碼

 

 

  • 再設計一個用戶和角色一對多的關聯表 user_roles 一個用戶能夠擁有多個角色 

     


建立 Spring Security和 JWT加持的 Web工程

  • pom.xml 中引入 Spring Security和 JWT所必需的依賴

  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-security</artifactId>

  4. </dependency>

  5. <dependency>

  6. <groupId>io.jsonwebtoken</groupId>

  7. <artifactId>jjwt</artifactId>

  8. <version>0.9.0</version>

  9. </dependency>

  • 項目配置文件中加入數據庫和 JPA等須要的配置

  1. server.port=9991

  2. spring.datasource.driver-class-name=com.mysql.jdbc.Driver

  3. spring.datasource.url=jdbc:mysql://121.196.XXX.XXX:3306/spring_security_jwt?useUnicode=true&characterEncoding=utf-8

  4. spring.datasource.username=root

  5. spring.datasource.password=XXXXXX

  6. logging.level.org.springframework.security=info

  7. spring.jpa.hibernate.ddl-auto=update

  8. spring.jpa.show-sql=true

  9. spring.jackson.serialization.indent_output=true

  • 建立用戶、角色實體

用戶實體 User

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Entity

  6. public class User implements UserDetails {

  7. @Id

  8. @GeneratedValue

  9. private Long id;

  10. private String username;

  11. private String password;

  12. @ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)

  13. private List<Role> roles;

  14. ...

  15. // 下面爲實現UserDetails而須要的重寫方法!

  16. @Override

  17. public Collection<? extends GrantedAuthority> getAuthorities() {

  18. List<GrantedAuthority> authorities = new ArrayList<>();

  19. for (Role role : roles) {

  20. authorities.add( new SimpleGrantedAuthority( role.getName() ) );

  21. }

  22. return authorities;

  23. }

  24. ...

  25. }

此處所建立的 User類繼承了 Spring Security的 UserDetails接口,從而成爲了一個符合 Security安全的用戶,即經過繼承 UserDetails,便可實現 Security中相關的安全功能。

角色實體 Role:

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Entity

  6. public class Role {

  7. @Id

  8. @GeneratedValue

  9. private Long id;

  10. private String name;

  11. ... // 省略 getter和 setter

  12. }

  • 建立JWT工具類

主要用於對 JWT Token進行各項操做,好比生成Token、驗證Token、刷新Token等

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Component

  6. public class JwtTokenUtil implements Serializable {

  7. private static final long serialVersionUID = -5625635588908941275L;

  8. private static final String CLAIM_KEY_USERNAME = "sub";

  9. private static final String CLAIM_KEY_CREATED = "created";

  10. public String generateToken(UserDetails userDetails) {

  11. ...

  12. }

  13. String generateToken(Map<String, Object> claims) {

  14. ...

  15. }

  16. public String refreshToken(String token) {

  17. ...

  18. }

  19. public Boolean validateToken(String token, UserDetails userDetails) {

  20. ...

  21. }

  22. ... // 省略部分工具函數

  23. }

  • 建立Token過濾器,用於每次外部對接口請求時的Token處理

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Component

  6. public class JwtTokenFilter extends OncePerRequestFilter {

  7. @Autowired

  8. private UserDetailsService userDetailsService;

  9. @Autowired

  10. private JwtTokenUtil jwtTokenUtil;

  11. @Override

  12. protected void doFilterInternal ( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

  13. String authHeader = request.getHeader( Const.HEADER_STRING );

  14. if (authHeader != null && authHeader.startsWith( Const.TOKEN_PREFIX )) {

  15. final String authToken = authHeader.substring( Const.TOKEN_PREFIX.length() );

  16. String username = jwtTokenUtil.getUsernameFromToken(authToken);

  17. if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

  18. UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

  19. if (jwtTokenUtil.validateToken(authToken, userDetails)) {

  20. UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(

  21. userDetails, null, userDetails.getAuthorities());

  22. authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(

  23. request));

  24. SecurityContextHolder.getContext().setAuthentication(authentication);

  25. }

  26. }

  27. }

  28. chain.doFilter(request, response);

  29. }

  30. }

  • Service業務編寫

主要包括用戶登陸和註冊兩個主要的業務

  1. public interface AuthService {

  2. User register( User userToAdd );

  3. String login( String username, String password );

  4. }

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Service

  6. public class AuthServiceImpl implements AuthService {

  7. @Autowired

  8. private AuthenticationManager authenticationManager;

  9. @Autowired

  10. private UserDetailsService userDetailsService;

  11. @Autowired

  12. private JwtTokenUtil jwtTokenUtil;

  13. @Autowired

  14. private UserRepository userRepository;

  15. // 登陸

  16. @Override

  17. public String login( String username, String password ) {

  18. UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );

  19. final Authentication authentication = authenticationManager.authenticate(upToken);

  20. SecurityContextHolder.getContext().setAuthentication(authentication);

  21. final UserDetails userDetails = userDetailsService.loadUserByUsername( username );

  22. final String token = jwtTokenUtil.generateToken(userDetails);

  23. return token;

  24. }

  25. // 註冊

  26. @Override

  27. public User register( User userToAdd ) {

  28. final String username = userToAdd.getUsername();

  29. if( userRepository.findByUsername(username)!=null ) {

  30. return null;

  31. }

  32. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

  33. final String rawPassword = userToAdd.getPassword();

  34. userToAdd.setPassword( encoder.encode(rawPassword) );

  35. return userRepository.save(userToAdd);

  36. }

  37. }

  • Spring Security配置類編寫(很是重要)

這是一個高度綜合的配置類,主要是經過重寫 WebSecurityConfigurerAdapter 的部分 configure配置,來實現用戶自定義的部分。

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Configuration

  6. @EnableWebSecurity

  7. @EnableGlobalMethodSecurity(prePostEnabled=true)

  8. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  9. @Autowired

  10. private UserService userService;

  11. @Bean

  12. public JwtTokenFilter authenticationTokenFilterBean() throws Exception {

  13. return new JwtTokenFilter();

  14. }

  15. @Bean

  16. public AuthenticationManager authenticationManagerBean() throws Exception {

  17. return super.authenticationManagerBean();

  18. }

  19. @Override

  20. protected void configure( AuthenticationManagerBuilder auth ) throws Exception {

  21. auth.userDetailsService( userService ).passwordEncoder( new BCryptPasswordEncoder() );

  22. }

  23. @Override

  24. protected void configure( HttpSecurity httpSecurity ) throws Exception {

  25. httpSecurity.csrf().disable()

  26. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

  27. .authorizeRequests()

  28. .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // OPTIONS請求所有放行

  29. .antMatchers(HttpMethod.POST, "/authentication/**").permitAll() //登陸和註冊的接口放行,其餘接口所有接受驗證

  30. .antMatchers(HttpMethod.POST).authenticated()

  31. .antMatchers(HttpMethod.PUT).authenticated()

  32. .antMatchers(HttpMethod.DELETE).authenticated()

  33. .antMatchers(HttpMethod.GET).authenticated();

  34. // 使用前文自定義的 Token過濾器

  35. httpSecurity

  36. .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter

相關文章
相關標籤/搜索