일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Play2
- 파이썬 데이터분석
- play2 강좌
- 하이브리드앱
- 스칼라 동시성
- 주키퍼
- CORDA
- 엔터프라이즈 블록체인
- 이더리움
- 스위프트
- 스칼라 강좌
- Hyperledger fabric gossip protocol
- 파이썬 동시성
- Golang
- 안드로이드 웹뷰
- Actor
- Play2 로 웹 개발
- 스칼라
- 그라파나
- play 강좌
- 파이썬 강좌
- Akka
- hyperledger fabric
- Adapter 패턴
- 하이퍼레저 패브릭
- 파이썬 머신러닝
- 파이썬
- 블록체인
- akka 강좌
- 플레이프레임워크
- Today
- Total
HAMA 블로그
Spring for android - 예제 소스 분석 (2) 본문
서버분석
아래과 같은 라이브러리가 필요합니다.
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile("org.springframework.boot:spring-boot-starter-security")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
특이한건 스프링 boot 를 사용했다는건데, 이번 예제를 통해서 저도 처음 boot 를 접해보았는데 설정부분 및 REST 서비스를 굉장히 단순화 시켜 놓았더군요.
스프링 부트를 써야하는 이유 : http://start.goodtime.co.kr/2014/10/%EC%9D%B4%EC%A0%9C%EB%8A%94-spring-boot%EB%A5%BC-%EC%8D%A8%EC%95%BC%ED%95%A0-%EB%95%8C%EB%8B%A4/
XML 설정파일은 필요없고, 어노테이션을 통해서 설정을 해줍니다.
(자바 코드를 통한 설정은 스프링 3.1에 거의 완성되었습니다.)
중요한 2개의 자바 파일을 분석해 보겠습니다.(Application.java 에서 스프링을 실행시켜줍니다.)
1. WebSecurityConfiguration
첫번째 소스를 분석하기전에 기본 지식을 쌓도록 합시다.
(http://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/ )
WebSecurityConfigurerAdapter
@EnableWebSecurity
어노테이션과 WebSecurityConfigurerAdapter
은 웹기반 보안을 위해 함께 작동 하며 WebSecurityConfigurerAdapter
을 상속받아서 몇줄만 코딩하면 다음과 같은것을 할수있게된다.
- 우리의 어플리케이션에 어떤 URL 로 들어오던간에 사용자에게 인증을 요구할수있음.
- 사용자이름과 비밀번호 및 롤기반으로 사용자를 생성할수있다.
- HTTP Basic 과 폼기반 인증을 가능하게함.
- 스프링 시큐리티는 자동적으로 로그인페이지,인증실패 url 등을 제공한다.
@EnableWebSecurity
@Configuration
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user") // #1
.password("password")
.roles("USER")
.and()
.withUser("admin") // #2
.password("password")
.roles("ADMIN","USER");
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**"); // #3
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/signup","/about").permitAll() // #4
.antMatchers("/admin/**").hasRole("ADMIN") // #6
.anyRequest().authenticated() // 7
.and()
.formLogin() // #8
.loginUrl("/login") // #9
.permitAll(); // #5
}
}
- #1 사용자 이름 “user” 에 대해서 유저ROLE로 인메모리 인증을 허락한다.
- #2 사용자 이름 “admin” 에 대해 관리자ROLE로 인메모리 인증을 부여한다.
- #3 “/resources/” 경로 호출은 인증 무시한다. XML 설정에서는 다음과 같다. http@security=none
- #4 인증없는 사용자를 포함한 누구한테나 “/signup” and “/about” 경로는 허용한다.
- #5 “/login” and “/login?error” 경로는 누구한테나 허용
- #6 /admin/ 로 시작되는 경로는 반드시 관리자 ROLE 에게만 허용된다.
- #7 모든 남은 URLs 는 인증이 성공해야지만 접근가능하다.
- #8 자바 세팅을 통해 폼기반인증셋업. 인증은 /login 으로 username/password 가 POST 로 날라올때
수행된다. - #9 명시적으로 GET /login 이 요청되었을때 로그인페이지를 보여지는게 요구되어진다.
위의 것은 아래와 같은 기존의 XML based configuration 과 비슷하다.
<http security="none" pattern="/resources/**"/>
<http use-expressions="true">
<intercept-url pattern="/logout" access="permitAll"/>
<intercept-url pattern="/login" access="permitAll"/>
<intercept-url pattern="/signup" access="permitAll"/>
<intercept-url pattern="/about" access="permitAll"/>
<intercept-url pattern="/**" access="hasRole('ROLE_USER')"/> #7
<logout
logout-success-url="/login?logout"
logout-url="/logout"
/>
<form-login
authentication-failure-url="/login?error"
login-page="/login"
login-processing-url="/login"
password-parameter="password"
username-parameter="username"
/>
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user" #1
password="password"
authorities="ROLE_USER"/>
<user name="admin" #2
password="password"
authorities="ROLE_USER,ROLE_ADMIN"/>
</user-service>
</authentication-provider>
</authentication-manager>
XML 설정과 몇가지 차이점
- #1 , #2 에서 보면 자바설정에서는 ROLE_ 이 없다. 자동적으로 추가해주기때문이다.
- Java configuration has different defaults URLs and parameters. Keep this in mind when creating custom login pages. The result is that our URLs are more RESTful. Additionally, it is not quite so obvious we are using Spring Security which helps to prevent information leaks. For example:
- /spring_security_login 대신해서 GET /login 호출하면 로그인 페이지를 연다.
- /j_spring_security_check 대신해서 POST /login 를 호출하면 인증체크를 한다.
- 사용자이름 기본 설정은 j_username 대신해서 username 이다.
- 패스워드 기본 설정은 j_password 대신해서 password 이다.
- 자바 설정은 여러가지 요청 URL 에 대하여 동일한 롤에 의해 쉽게 매핑할수있게 해준다. #4 참고
- 자바설정은 여분의 쓸모없는 코드들을 제거한다 . intercept-url 같은 경우 중복이 심하다.
- When mapping HTTP requests using the hasRole method as we did in #6, we do not need to specify “ROLE_” as we would in XML. Again, this is so common of a convention that the hasRole method automatically adds “ROLE_” for you. If you did not want to automatically prefix with “ROLE_”, you could use the “access” method.
실제 코드
package showcase;
// org.springframework.security 를 사용합니다.
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;
// 스프링 시큐리티 Java Config 설정 (security-context.xml 로 설정할수도있다.)
// configuration 은 springSecurityFilterChain
로 알려진 서블릿 필터를 생성한다.
// configuration 은 URLs 보호, ID/PWD 검증 같은 모든 보안에 대해서 책임진다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication() // roy , spring 이면 인증 성공, USER 롤 받음
.withUser("roy")
.password("spring")
.roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**") // 모든 요청에 대해서 USER 롤을 가지고있는지 확인
.hasRole("USER").and().httpBasic();
}
}
둘째. HomeController
package showcase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/*")
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home() {
logger.info("Spring Android Basic Auth");
return "home"; // home.html 호출 해줌
}
@RequestMapping(value = "/getmessage", method = RequestMethod.GET, produces = "application/json")
public @ResponseBody Message getMessage() {
logger.info("Accessing protected resource"); // USER 롤을 가지고있으면 여기에 들어옴
// response body 로 Message 객체를 JSON 형식으로 보내줌.
return new Message(100, "Congratulations!", "You have accessed a Basic Auth protected resource.");
}
}
클라이언트 분석
아래과 같은 라이브러리가 필요합니다.
dependencies {
compile 'org.springframework.android:spring-android-rest-template:1.0.1.RELEASE'
compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.11'
}
안드로이드 코드를 살펴보기전에 간련 기술을 알아봅시다. (아래 링크 참고)
http://zgundam.tistory.com/43 (스프링 시큐리티 강좌 )
http://www.srccodes.com/p/article/36/spring-security-http-basic-authentication-example (HTTP 기본 인증에 관하여)
http://www.mkyong.com/spring-security/spring-security-form-login-using-database/ (JDBC 기반 인증에 관하여 )
HTTP basic Authentication
HTTP 기본 인증에서는 서버로 아래와 같이 Request Header 를 보낸다.
1 2 3 4 5 6 7 8 9 10 | GET /spring-security-http-basic-authentication/secured/mypage HTTP /1 .1 Host: localhost:8080 User-Agent: Mozilla /5 .0 (Windows NT 6.1; WOW64; rv:17.0) Gecko /20100101 Firefox /17 .0 Accept: text /html ,application /xhtml +xml,application /xml ;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip , deflate DNT: 1 Connection: keep-alive Cookie: JSESSIONID=896331E26095C95449516FCBF2E0E93C; __atuvc=28%7C31%2C0%7C32%2C0%7C33%2C215%7C34%2C59%7C35 Authorization: Basic c3JjY29kZXM6cGFzc3dvcmQ= |
실제 코드
package org.springframework.android.basicauth;
import java.util.Collections;
import org.springframework.http.HttpAuthentication;
import org.springframework.http.HttpBasicAuthentication;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
/**
* @author Roy Clarkson
*/
public class MainActivity extends AbstractAsyncActivity {
protected static final String TAG = MainActivity.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity_layout);
// Initiate the request to the protected service
final Button submitButton = (Button) findViewById(R.id.submit);
submitButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
new FetchSecuredResourceTask().execute();
}
});
}
private void displayResponse(Message response) {
Toast.makeText(this, response.getText(), Toast.LENGTH_LONG).show();
}
private class FetchSecuredResourceTask extends AsyncTask<Void, Void, Message> {
private String username;
private String password;
@Override
protected void onPreExecute() {
showLoadingProgressDialog();
// build the message object
EditText editText = (EditText) findViewById(R.id.username);
this.username = editText.getText().toString();
editText = (EditText) findViewById(R.id.password);
this.password = editText.getText().toString();
}
@Override
protected Message doInBackground(Void... params) {
final String url = getString(R.string.base_uri) + "/getmessage";
// HTTP 기본인증헤더를 username , password를 이용하여 생성한다.
HttpAuthentication authHeader = new HttpBasicAuthentication(username, password);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAuthorization(authHeader);
requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();// JSON 형식 컨버터 설정
restTemplate.getMessageConverters().add(new MappingJacksonHttpMessageConverter());
try {
// 서버의 URL에 데이터를 요청한다.받은데이터는 Message 객체로 변환
Log.d(TAG, url);
ResponseEntity<Message> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<Object>(requestHeaders), Message.class);
return response.getBody();
} catch (HttpClientErrorException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
return new Message(0, e.getStatusText(), e.getLocalizedMessage());
} catch (ResourceAccessException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
return new Message(0, e.getClass().getSimpleName(), e.getLocalizedMessage());
}
}
@Override
protected void onPostExecute(Message result) {
dismissProgressDialog();
displayResponse(result);
}
}
}
'Spring' 카테고리의 다른 글
Angular JS and Spring Security 1 ~4 편 (번역) (0) | 2015.07.11 |
---|---|
Mybatis-spring 버전별 의존성 요구사항 (0) | 2015.07.10 |
component-scan / annotation-config / annotation-driven 차이점 (2) | 2015.07.09 |
Spring for android - 예제 실행해보기 (1) (1) | 2015.07.03 |
인증토큰을 이용한 Restful 스프링 보안 (번역) (1) | 2015.06.02 |