Skip to content

Commit

Permalink
[server-rust]支持JWT认证授权 #6
Browse files Browse the repository at this point in the history
  • Loading branch information
itang committed Feb 11, 2016
1 parent 748ae60 commit d2eb76b
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 12 deletions.
2 changes: 2 additions & 0 deletions server/rust/deftype-server-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ dotenv = "0.8.0"
dotenv_macros = "0.8.0"
hyper = "0.7.2"
iron = "0.2.6"
jwt = "0.4.0"
lazy_static = "0.1.15"
mount = "0.0.9"
r2d2 = "0.6.3"
r2d2-diesel = "0.5.0"
router = "0.1.0"
rust-crypto = "0.2.34"
serde = "0.6.11"
serde_json = "0.6.0"
serde_macros = "0.6.11"
Expand Down
8 changes: 4 additions & 4 deletions server/rust/deftype-server-rs/Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ end
namespace :run do
desc 'dev'
task :dev do
fork do
sleep(2)
sh 'xdg-open http://localhost:3000 > /dev/null'
end
# fork do
# sleep(2)
# sh 'xdg-open http://localhost:3000 > /dev/null'
# end
sh 'cargo run --bin deftype-server-rs'
end
task :prod do
Expand Down
40 changes: 34 additions & 6 deletions server/rust/deftype-server-rs/public/assets/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,54 @@ var api = (function(){
mode: function(){
return $.getJSON("/api/server/mode");
}
},
users: {
login: function(name, password){
var login_form ={login_name: name, password: password};
return $.ajax({
url: "/api/users/login",
data: JSON.stringify(login_form),
contentType : 'application/json',
method: "POST",
dataType: "json"
});
}
}
};

return ret;
})();

$(function(){
$("#msg").load("/api");
var token = new Date();
$.ajaxSetup( {beforeSend: function(jqXHR) {
jqXHR.setRequestHeader("Authorization", "Bearer " + token);
console.log(token);
} } );

function load_server_time(){
api.server.time().done(function(ret){
$("#time").html("server time:" + ret.data.now);
});
}

setInterval(load_server_time, 1000);
api.users.login("admin", "123456").done(function(ret){
console.log(JSON.stringify(ret));
if(ret.ok){
token = ret.data.token;
$("#msg").load("/api");

setInterval(load_server_time, 1000);

api.server.mode().done(function(ret){
if(ret.data == "development"){
$("#dev").load("/dev/1.html").show();
}
});
}else{

api.server.mode().done(function(ret){
if(ret.data == "development"){
$("#dev").load("/dev/1.html").show();
}
});
}).fail(function( jqXHR, textStatus ) {
alert( "Request failed: " + textStatus );
});
});
15 changes: 15 additions & 0 deletions server/rust/deftype-server-rs/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ pub fn users_create(req: &mut Request) -> IronResult<Response> {
}
}

pub fn users_login(req: &mut Request) -> IronResult<Response> {
let parsed = req.get::<bodyparser::Struct<models::LoginForm>>();
let parsed = try!(parsed.map_err(BodyErrorWrapper));
match parsed {
Some(ref login_form) => {
let conn = try!(global::conn_pool().get().map_err(GetTimeoutWrapper));
match models::login(&conn, login_form) {
Some(user) => ResultDTO::ok(&user).json_result(),
None => ResultDTO::err("用户不存在或者密码输入有误").json_result(),
}
}
None => ResultDTO::err("").json_result(),
}
}

pub fn dev_mock_error(_: &mut Request) -> IronResult<Response> {
Err(IronError::new(MockError, status::InternalServerError))
}
5 changes: 5 additions & 0 deletions server/rust/deftype-server-rs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ extern crate chrono;
extern crate lazy_static;
extern crate bodyparser;
extern crate bcrypt;
extern crate jwt;
extern crate crypto;


use std::{io, process};
use std::io::prelude::*;
Expand Down Expand Up @@ -50,6 +53,7 @@ fn main() {

api_router.get("/users", handlers::users_list);
api_router.post("/users", handlers::users_create);
api_router.post("/users/login", handlers::users_login);

let mut mount = Mount::new();
mount.mount("/api", api_router);
Expand All @@ -64,6 +68,7 @@ fn main() {

let mut chain = Chain::new(mount);
chain.link_before(middlewares::Runtime);
chain.link_before(middlewares::JwtFilter);
chain.link_after(middlewares::ErrorsHandler);
chain.link_after(middlewares::Runtime);

Expand Down
43 changes: 42 additions & 1 deletion server/rust/deftype-server-rs/src/middlewares.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ use iron::prelude::*;
use router::NoRoute;
use time::precise_time_ns;

use util::*;
use hyper::header;
use hyper::header::{Authorization, Bearer};
use crypto::sha2::Sha256;
use jwt::{Header, Registered, Token};

use util::*;
use models;

pub struct Runtime;

Expand Down Expand Up @@ -48,3 +53,39 @@ impl AfterMiddleware for ErrorsHandler {
}
}
}


pub struct JwtFilter;

impl BeforeMiddleware for JwtFilter {
fn before(&self, req: &mut Request) -> IronResult<()> {
let uri = &req.url.path.join("/");
if uri.starts_with("api/") && !uri.starts_with("api/users/login") {
// Get the full Authorization header from the incoming request headers
let auth_header = match req.headers.get::<Authorization<Bearer>>() {
Some(header) => header,
None => panic!("No authorization header found"),
};

// Format the header to only take the value
let jwt = header::HeaderFormatter(auth_header).to_string();

// We don't need the Bearer part,
// so get whatever is after an index of 7
let jwt_slice = &jwt[7..];

// Parse the token
let token = Token::<Header, Registered>::parse(jwt_slice).unwrap();

// Get the secret key as bytes
let secret = models::AUTH_SECRET.as_bytes();
// Verify the token
if !token.verify(&secret, Sha256::new()) {
return Err(IronError::new(StringError("授权不通过!".to_string()),
(status::Forbidden, "授权不通过!")));
}
}

Ok(())
}
}
59 changes: 58 additions & 1 deletion server/rust/deftype-server-rs/src/models.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use diesel;
use diesel::prelude::*;
use diesel::pg::PgConnection;

use std::default::Default;
use crypto::sha2::Sha256;
use jwt::{Header, Registered, Token};

use util::*;

pub const AUTH_SECRET: &'static str = "uLkvkYvgiA01ozKoTvyyXL_YBZUxDQK0OGosXmdBg84=";

pub fn create_user(conn: &PgConnection, new_user: &NewUser) -> User {
use schema::users;
Expand Down Expand Up @@ -30,8 +36,38 @@ pub fn find_users(conn: &PgConnection) -> Vec<User> {
.expect("Error loading user")
}

pub fn login(conn: &PgConnection, login_form: &LoginForm) -> Option<LoginResponse> {
use schema::users::dsl::*;
let ret = users.filter(login_name.eq(&login_form.login_name))
.limit(1)
.load::<User>(conn)
.expect("Error loading user");
match ret.first() {
Some(user) => {
if bcrypt_verify(&login_form.password, &user.password) {
let header: Header = Default::default();
// For the example, we just have one claim
// You would also want iss, exp, iat etc
let claims = Registered {
sub: Some(user.login_name.clone()),
..Default::default()
};
let token = Token::new(header, claims);
// Sign the token
let jwt = token.signed(AUTH_SECRET.as_bytes(), Sha256::new()).unwrap();
let token_str = format!("{}", jwt);

Some(LoginResponse::new(user.clone(), token_str))
} else {
None
}
}
None => None,
}
}


#[derive(Queryable, Debug, Serialize)]
#[derive(Queryable, Debug, Serialize, Clone)]
pub struct User {
pub id: i32,
pub login_name: String,
Expand All @@ -49,6 +85,27 @@ pub struct NewUser {
pub password: String,
}

#[derive(Debug, Deserialize, Clone)]
pub struct LoginForm {
pub login_name: String,
pub password: String,
}

#[derive(Debug, Serialize, Clone)]
pub struct LoginResponse {
pub user: User,
pub token: String,
}

impl LoginResponse {
pub fn new(user: User, token: String) -> Self {
LoginResponse {
user: user,
token: token,
}
}
}

type ValidResult<T> = Result<T, String>;

trait Valid<T> {
Expand Down
22 changes: 22 additions & 0 deletions server/rust/deftype-server-rs/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ use types::ResultDTO;
// }
// }

#[derive(Debug)]
pub struct StringError(pub String);

impl fmt::Display for StringError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}

impl StdError for StringError {
fn description(&self) -> &str {
&*self.0
}
}

pub struct JsonEncodeErrorWrapper(pub JsonError);

impl From<JsonEncodeErrorWrapper> for IronError {
Expand Down Expand Up @@ -97,6 +112,13 @@ pub fn bcrypt_hash(value: &str) -> BcryptResult {
bcrypt::hash(value, bcrypt::DEFAULT_COST).map_err(|e| format!("{:?}", e))
}

pub fn bcrypt_verify(value: &str, hashed: &str) -> bool {
match bcrypt::verify(value, hashed) {
Ok(valid) => valid,
Err(_) => false,
}
}

header! { (XMLHttpRequest, "X-Requested-With") => [String] }

header! { (XRuntime, "X-Runtime") => [String] }
Expand Down

0 comments on commit d2eb76b

Please sign in to comment.