基於Spring秒速賽 車網站出 租和 JWT的權限系統設計

關於秒速賽 車網站出 租 Q1157880099 源碼地址:haozbbs.com系統的認證和權限模塊也算是一個系統的基礎設施了,幾乎任何的互聯網服務都會涉及到這方面的要求。在Java EE領域,成熟的安全框架解決方案一般有 Apache Shiro、Spring Security等兩種技術選型。Apache Shiro簡單易用也算是一大優勢,但其功能還是遠不如 Spring Security強大。Spring Security可以爲 Spring 應用提供聲明式的安全訪問控制,起通過提供一系列可以在 Spring應用上下文中可配置的Bean,並利用 Spring IoC和 AOP等功能特性來爲應用系統提供聲明式的安全訪問控制功能,減少了諸多重複工作。

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

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

本文實驗環境如下:

Spring Boot版本:2.0.6.RELEASE
IDE:IntelliJ IDEA 2018.2.4
另外本文實驗代碼置於文尾,需要自取。

設計用戶和角色
本文實驗爲了簡化考慮,準備做如下設計:

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

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

再設計一個用戶和角色一對多的關聯表user_roles 一個用戶可以擁有多個角色 用戶-角色對應表
創建 Spring Security和 JWT加持的 Web工程
pom.xml 中引入 Spring Security和 JWT所必需的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
項目配置文件中加入數據庫和 JPA等需要的配置
server.port=9991

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://121.196.XXX.XXX:3306/spring_security_jwt?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=XXXXXX

logging.level.org.springframework.security=info

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jackson.serialization.indent_output=true
創建用戶、角色實體
用戶實體 User:

/**

角色實體 Role:

/**

/**

  • @ www.codesheep.cn
  • 20190312br/>*/
    @Component
    public class JwtTokenUtil implements Serializable {

    private static final long serialVersionUID = -5625635588908941275L;

    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";

    public String generateToken(UserDetails userDetails) {
    ...
    }

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

    public String refreshToken(String token) {
    ...
    }

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

    ... // 省略部分工具函數
    }
    創建Token過濾器,用於每次外部對接口請求時的Token處理
    /**

  • @ www.codesheep.cn
  • 20190312br/>*/
    @Component
    public class JwtTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

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

    String authHeader = request.getHeader( Const.HEADER_STRING );
    if (authHeader != null && authHeader.startsWith( Const.TOKEN_PREFIX )) {
        final String authToken = authHeader.substring( Const.TOKEN_PREFIX.length() );
        String username = jwtTokenUtil.getUsernameFromToken(authToken);
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                            request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
        }
    }
    chain.doFilter(request, response);

    }
    }
    Service業務編寫
    主要包括用戶登錄和註冊兩個主要的業務

public interface AuthService {
User register( User userToAdd );
String login( String username, String password );
}
/**

/**

  • @ www.codesheep.cn
  • 20190312br/>*/
    @Configuration
    @EnableWebSecurity
    br/>@EnableGlobalMethodSecurity(prePostEnabled=true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Bean
    public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
    return new JwtTokenFilter();
    }

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
    }

    @Override
    protected void configure( AuthenticationManagerBuilder auth ) throws Exception {
    auth.userDetailsService( userService ).passwordEncoder( new BCryptPasswordEncoder() );
    }

    @Override
    protected void configure( HttpSecurity httpSecurity ) throws Exception {
    httpSecurity.csrf().disable()
    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
    .authorizeRequests()
    .antMatchers(HttpMethod.OPTIONS, "/").permitAll() // OPTIONS請求全部放行
    .antMatchers(HttpMethod.POST, "/authentication/
    ").permitAll() //登錄和註冊的接口放行,其他接口全部接受驗證
    .antMatchers(HttpMethod.POST).authenticated()
    .antMatchers(HttpMethod.PUT).authenticated()
    .antMatchers(HttpMethod.DELETE).authenticated()
    .antMatchers(HttpMethod.GET).authenticated();

    // 使用前文自定義的 Token過濾器
    httpSecurity
            .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
    
    httpSecurity.headers().cacheControl();

    }
    }
    編寫測試 Controller
    登錄和註冊的 Controller:

/**

  • @ www.codesheep.cn
  • 20190312br/>*/
    @RestController
    public class JwtAuthController {
    br/>@Autowired
    private AuthService authService;

    // 登錄
    @RequestMapping(value = "/authentication/login", method = RequestMethod.POST)
    public String createToken( String username,String password ) throws AuthenticationException {
    return authService.login( username, password ); // 登錄成功會返回JWT Token給用戶
    }

    // 註冊
    @RequestMapping(value = "/authentication/register", method = RequestMethod.POST)
    public User register( @RequestBody User addedUser ) throws AuthenticationException {
    return authService.register(addedUser);
    }
    }
    再編寫一個測試權限的 Controller:

/**

接下來啓動工程,實驗測試看看效果

實驗驗證
在文章開頭我們即在用戶表 user中插入了一條用戶名爲 codesheep的記錄,並在用戶-角色表 user_roles中給用戶 codesheep分配了普通角色(ROLE_NORMAL)和管理員角色(ROLE_ADMIN)

接下來進行用戶登錄,並獲得後臺向用戶頒發的JWT Token

用戶登錄並獲得JWT Token

接下來訪問權限測試接口
不帶 Token直接訪問需要普通角色(ROLE_NORMAL)的接口 /normal/test會直接提示訪問不通:

不帶token訪問是不通的

而帶 Token訪問需要普通角色(ROLE_NORMAL)的接口 /normal/test纔會調用成功:

帶token訪問OK

同理由於目前用戶具備管理員角色,因此訪問需要管理員角色(ROLE_ADMIN)的接口 /admin/test也能成功:

訪問需要管理員角色的接口OK

接下里我們從用戶-角色表裏將用戶codesheep的管理員權限刪除掉,再訪問接口 /admin/test,會發現由於沒有權限,訪問被拒絕了:

由於權限不夠而被拒絕

經過一系列的實驗過程,也達到了我們的預期!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章