티스토리 툴바


Java/Grails2012/02/17 14:57
스프링 처럼 DI를 받을 수 있는데...

FBUserDetailService를 di 받아서 사용할 경우 일반적으로 
def fbUserDetailService 이런 형식의 카멜 표기법으로 정의하는것으로 생각한다.

하지만 되지 않아!!!!!

http://grails.org/doc/latest/guide/services.html#dependencyInjectionServices 

저기 문서의 가운데를 보면

NOTE: Normally the property name is generated by lower casing the first letter of the type. For example, an instance of the BookService class would map to a property named bookService.

To be consistent with standard JavaBean conventions, if the first 2 letters of the class name are upper case, the property name is the same as the class name. For example, the property name of theJDBCHelperService class would be JDBCHelperService, not jDBCHelperService or jdbcHelperService.

See section 8.8 of the JavaBean specification for more information on de-capitalization rules.



JDBCHelperService를 DI 받을려면 JDBCHelperService로 작명을 해주어야 한다고 한다.

뭐 그럼 나도 사용할 때

def FBUserDetailService 로 하면 됨 @_@
Posted by 김제준
TAG Grails
Java/Grails2012/02/09 15:02
Grails 2.0에서 spring security 플러그인을 지원 하는데, 인증은 Fackbook을 통해서 하고 인증 정보는 spring security를 사용하는 인증 방법.

1. Grails Filter를 사용해서 인증 url을 필터링함.
2. 인증 url로 요청이 들어오고 인증이 안되어 있을 경우 Facebook을 통해 인증 하도록 함.

참고 URL
Grails Filter :http://grails.org/doc/latest/guide/theWebLayer.html#filters 
Facebook 인증 :  http://developers.facebook.com/docs/authentication/ 


FackbookAuthFilters의 코드(약간 자바 스타일 문법도 섞임)
쭉 보면 login으로 요청을 하는데 code 값이 없거나 미인증된 사용자면 Facebook을 통해서 인증을 하도록 한다.
Facebook에서 인증후 code값을 전달해 주는데, 이 code값을 다시 Fackbook을 통해서 accessToken을 받아 오도록 하고, 
이것을 가지고 사용자의 정보를 가져올 수 있다.

def filters = {

all(uri : '/login/**', uriExclude : '/logout/**') {

before = {

String code = params.code


SecurityContext context = SecurityContextHolder.context

String username = context.authentication.getName()


if (code == null && username.equals('anonymousUser')) {

redirect(url: "https://www.facebook.com/dialog/oauth?client_id=" + clientId + "&redirect_uri="+ redirectUri +"&scope=publish_stream,email")

return false

} else {

if (username.equals('anonymousUser')) {

String accessToken = null;

Integer expires = null;


def http = new HTTPBuilder( 'https://graph.facebook.com/oauth/' )


http.get( path: 'access_token',

query: [client_id: clientId, redirect_uri: redirectUri, client_secret: clientSecret, code: code] ) { resp, reader ->

String result = reader.text

String[] pairs = result.split("&")

for (String pair : pairs) {

String[] kv = pair.split("=")

if (kv.length != 2) {

throw new RuntimeException("Unexpected auth response")

} else {

if (kv[0].equals("access_token")) {

accessToken = kv[1]

}

if (kv[0].equals("expires")) {

expires = Integer.valueOf(kv[1])

}

}

}

}

if (checkAuthByAccessToken(accessToken, expires, request, response)) {

redirect(url: serviceUrl)

return false

}

}

}

}


 
Facebook을 통해서 인증 받은 사용자 정보가 서비스에 존재 하지 않은 사용자일경우 기본 롤로 생성을 하고 만약 있으면 조회를 한 후 스프링 시큐리티 세션에 넣어서 인증정보를 가지고 있도록 한다.

def boolean checkAuthByAccessToken(String accessToken, expires, request, response) {

if (accessToken != null && expires != null) {

def fb = new RESTClient('https://graph.facebook.com/')

def resp = fb.get(path: 'me', query: [ access_token: accessToken])

def email

def password = 'test'

ExampleUser user

if (resp.status == 200) {

email = resp.data.email

user = ExampleUser.findByUsername(email)

if (user == null) {

user = new ExampleUser(username: email, password : password, accountExpired: false, accountLocked: false, enabled: true ).save(flush : true)

Role role = Role.findByAuthority('ROLE_USER')

UserRole.create(user, role, true);

} else {

user = userDetailsService.loadUserByUsername(user.username, true)

}

}


UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user, password)

request.getSession().setAttribute(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(user.username));

authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request))


SecurityContextHolder.context.authentication = authRequest


return true

} else {

throw new RuntimeException("Access token and expires not found")

}

}


Posted by 김제준
Spring2011/12/26 17:57

http://blog.goyello.com/2011/12/16/enhancements-spring-mvc31/

역시 취미 삼아 ..

Spring 3.1이 개발된지 수개월이 지난 지금 드디어 릴리즈 됬다. 이번 배포 버전은 캐쉬 추상화, 빈 프로파일, 간단한 컨테이너 설정 등 몇가지 재미난 기능을 포함하고 있다. 아무튼 이 블로그 포스팅에서 스프링 3.1에서 내가 생각하는 TOP 5 개선에 대해서 설명할 것이다.

1. Flash Attributes support
플래쉬 어트리뷰트들은 요청 사이의 

2. @Validated annotation with support for JSR-303 validation groups.

스프링 MVC bean validation은 JSR303 그룹 벨리데이션을 지원하는 새로운 @Validated annotation을  지원한다. 이 그룹은 주어진 객체에 대해 검증을 하고 제약의 하위 집합을 정의 한다. 각 제약 조건에 대한 선언은 그룹이나 그것이 속한 그룹의 목록을 정의할 수 있다.


public class User {
    @NotNull
    private String name;
 
    @NotNull(groups = { Simple.class })
    private String address;
 
    @NotNull(groups = { Simple.class, Extended.class })
    private String licenceType;
 
    @NotNull(groups = { Extended.class })
    private String creditCard;
}



public interface Simple {
 
}
 
public interface Extended {
 
}

새로운 @Validated annotation에 대한 사용은 아래 예제에 나와 있다.


@Controller
public class MyController {
 
    @RequestMapping(value = "/user/default", method = RequestMethod.GET)
    public String createDefaultUser(@Validated({ Default.class }) User user, BindingResult result) {
        if(result.hasErrors()) {
            // Validation on 'name'
        }
        return null;
    }
 
    @RequestMapping(value = "/user/simple", method = RequestMethod.GET)
    public String createSimpleUser(@Validated({ Simple.class }) User user, BindingResult result) {
        if(result.hasErrors()) {
            // Validation on 'licenceType' and 'address'
        }
        return null;
    }
 
    @RequestMapping(value = "/user/extended", method = RequestMethod.GET)
    public String createExtendedUser(@Validated({ Simple.class, Extended.class }) User user, BindingResult result) {
        if(result.hasErrors()) {
            // Validation on 'creditCard' and 'licenceType' and 'address'
        }
 
        return null;
    }
}

스프링 3.1에 추가된 이 그룹 벨리데이션은 정말 편리하다. 이 벨리데이션 그룹에 대해서 더 많은 자료는 download and read the JSR 303 specification.

3. Support @Valid on @RequestBody method arguments

@RequestMapping 핸들러 메서드는 HTTP request body에 접근할 수 있는 @RequestBody 어노테이션을 지원한다. 변환이 가능한 메시지는 메시지 컨버터를 통해 변환이 이루어진다. FormHttpMessageConverter(application/x-www-form-urlencoded), MarshallingHttpMessageConverter(application/xml), MappingJacksonHttpMessageConverter(appliction/json) 그리도 다른 converter들도. 하지만 @RequestBody 어노테이션이 붙은 변수들은 자동적으로 벨리데이션체크를 하지 않는다. 수동으로 체크해주는 로직이 필요.


@Controller
public class MyController {
 
    @Autowired
    private Validator validator;
 
    @RequestMapping(value = "", method = RequestMethod.PUT)
    @ResponseStatus(value = HttpStatus.CREATED)
    @Secured("ROLE_ADMIN")
    @Transactional
    public void save(@RequestBody User user) {
        validate(user);
        dao.save(user);
    }
 
    private void validate(User user) {
        // perfom validation using injected validator
    }
}

스프링 3.1에서 @RequestBody 메서드 어노테이션은 @Valid 메서드를 통해 자동으로 벨리데이션 체크를 할 수 있다. 이 방법은 코드를 심플하게 만들어 준다.

@Controller
public class MyController {
 
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    @ResponseStatus(value = HttpStatus.CREATED)
    @Transactional
    public void save(@Valid @RequestBody User user) {
        dao.save(user);
    }
 
    @ExceptionHandler
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public String handleMethodArgumentNotValidException(
            MethodArgumentNotValidException error) {
        return "Bad request: " + error.getMessage();
    }
}


위의 예제에서 스프링은 자동으로 유효성검사를 수행하고 에러가 발생할 경우 MethodArgumentNotValidException을 던져준다. 추가적으로 @ExceptionHandler 메서드는 이러한 유형의 에러 처리에 대한 사용자 지정 동작을 추가하여 만들 수 있다.

4. Supporting PUT request with form encoded data

서브릿 구현의 한계는 인코딩된 HTTP PUT 요청에 대한 처리를 하지 않는 다는 것이다. 새롭게 소개되는  HttpPutFormContentFilter는 이 문제를 해결 했다. 스프링 3.1에서 RESTfull or RESTLike API 는 좀 더 심플하게 만들 수 있다. 이것에 대한 샘플은 이전 포스팅에서 찾아볼수 있다.
previous post to read more about the usage of HttpPutFormContentFilter

5. Java based configuration

기존의 Spring MVC 애플리케이션의 XML 설정은 완벽하게 자바 기반의 설정으로 변경할 수 있다. 가장 간단한 웹 애플리케이션의 설정은 @EnableWebMvc 어노테이션을 사용하는 것이다.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example" }, excludeFilters = @Filter(type = FilterType.ANNOTATION, value = Configuration.class))
@Import(PersistanceContextConfiguration.class)
public class ServletContextConfiguration extends WebMvcConfigurerAdapter {
 
    @Bean
    public TilesViewResolver configureTilesViewResolver() {
        return new TilesViewResolver();
    }
 
    @Bean
    public TilesConfigurer configureTilesConfigurer() {
        TilesConfigurer configurer = new TilesConfigurer();
        configurer.setDefinitions(new String[] { "/WEB-INF/tiles/tiles.xml",
                "/WEB-INF/views/**/views.xml" });
        return configurer;
    }
 
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("login");
    }
 
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/").addResourceLocations(
                "/recourses/**");
    }
 
    @Override
    public void configureDefaultServletHandling(
            DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}


WebMvcConfigurerAdapter에 대한 확장은 필요하지 않지만 기존 스프링 MVC의 설정방법도 허락된다. 어댑터는 추가적인 컴포넌트(포맷터,컨버터, 커스텀 벨리데이터, 인터셉터등)을 등록할 수 있도록 한다. 이 사용자 정의 설정은 자바 코드만들 사용하여 가능하다.  configuring a Spring MVC application with no xml in a Servlet 3.0 

Spring MVC 3.1  has a lot to offer.
스프링 MVC 3.1은 많은 향상점을 제공한다. 이 블로그에서 언급한 기능은 스프링 MVC와 애플리케이션 개발에 긍정적인 영향을 끼칠 것이다. 혁명적이지는 않지만 스프링 3.1에서 릴리즈된 모든 확장기능과 기능 향상에 대해서 보면 프레임 워크가 올바른 방향으로 가고 있다고 느낄수 있다. 캐쉬 추상화, 빈 프로파일, 설정 간소화 등 많은 핵심 기능들은 더 효과적으로 애플리케이션 개발을 할 수 있도록 할 것이다.

오메 발번역. ..
Posted by 김제준
Spring2011/10/05 11:37

@ResponseBody 이것을 붙이면 AnnotationMethodHandlerAdapter에 등록된 messageConverters 중 하나로 타입을 결정해서 뷰를 만들어 주게 된다.

만약 ContentNegotiatingViewResolver 를 만들어 주고
지원하는 mediaTypes 에 <entry key="xml" value="application/xml" /> 타입이 있어서
.xml 요청이 ContentNegotiatingViewResolver를 통해서 처리를 받고자 한다면

당연한 말이지만 @ResponseBody 를 컨트롤러 메서드에 붙이면 안된다.
ContentNegotiatingViewResolver 에 까지 요청이 도착 하기 전에 AnnotationMethodHandlerAdapter 가 받아서 처리해 버린다.

ie 와 FF, 크롬의 처리 방식이 각각 틀려서
FF와 크롬에서는 뭐 별 문제 없이 동작 하지만
IE에서는 .xml 요청을 보낼 경우 .xml 요청과 함께 */* 미디어 타입의 요청이 한번 더 날라가게 된다.

하여 결과 xml 을 볼 수가 없다.

@ResponseBody와 ContentNegotiatingViewResolver를 사용할때는 주의를 하도록 하자
Posted by 김제준