一.构建Spring Web应用

1.Spring MVC中用户请求处理

跟踪Spring MVC的请求 (1).png

    上图展示了请求使用Spring MVC所经历的所有站点。

    1:在请求离开浏览器时,会带有所有请求内容的信息,至少会包含请求的URL。

        请求通过Spring的DispatcherServlet前端控制器将请求发送给Spring MVC控制器(Controller)。

    2:DispatcherServlet会查询一个或多个处理器映射(handler mapping)来确定请求的下一站在哪里。

        处理器映射会根据请求所携带的URL信息来进行决策。

    3:一旦选择了合适的控制器(Controller),DispatcherServlet会将请求发送给选中的控制器。

        到了控制器,请求会卸下提交的信息并等待控制器处理这些信息。

    4:控制器处理完之后会产生一些信息即模型(model)。控制器将模型数据打包,并标示出用于渲染输出的视图名。然后将请求连同模型和视图名发送回DispatcherServlet。

    5:DispatcherServlet会使用视图解析器(view resolve)来将逻辑视图名匹配为一个特定的视图实现。

    6:视图的实现,交付模型数据。

    7:视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端。

2.基本控制器

@Controller:声明为一个控制器;

@RequestMapping:声明他们所要处理的请求:

    @RequestMapping(vaule="/", method=GET):当收到对"/"的HTTP GET请求时,就会调用home()该注解的方法。

@RequestParam:接收请求输入,将请求作为查询参数;

@PathVariable:使用占位符,将请求作为路径的一部分;

    两者不同点使用@RequestParam时,URL是这样的:

                        使用@PathVariable时,URL是这样的:

@RequestMapping(value="/user",method = RequestMethod.GET)     public @ResponseBody     User printUser(@RequestParam(value = "id", required = false, defaultValue = "0")     int id) {      User user = new User();         user = userService.getUserById(id);         return user;     }          @RequestMapping(value="/user/{id:\\d+}",method = RequestMethod.GET)     public @ResponseBody     User printUser2(@PathVariable int id) {         User user = new User();         user = userService.getUserById(id);         return user;     }

    

@Valid:告知Spring,需要确保这个对象满足校验限制。

    Java校验API所提供的校验注解:

注解 描述
@AssertFalse 所注解的元素必须是Boolean类型,并且值为false
@AssertTrue 所注解的元素必须是Boolean类型,并且值为true
@DecimalMax 所注解的元素必须是数字,并且值要小于或等于给定的BigDecimalString值
@DecimalMin 所注解的元素必须是数字,并且值要大于或等于给定的BigDecimalString
@Digits 所注解的元素必须是数字,并且它的值必须有指定的位数
@Future 所注解的元素的值必须是一个将来的日期
@Max 所注解的元素必须是数字,并且它的值要大于或等于给定的值
@Min 所注解的元素必须是数字,并且它的值要小于或等于给定的值
@NotNull 所注解元素的值必须不能为null
@Null 所注解元素的值必须为null
@Past 所注解的元素的值必须是一个已过去的日期
@Parttern 所注解的元素的值必须匹配给定的正则表达式
@Size 所注解的元素的值必须是String、集合或数组,并且它的长度要符合给定的范围

@Controller@RequestMapping(DetailController.VIEW_PREFIX)public class DetailController extends AjaxBaseController {public static final String VIEW_PREFIX = "/ajax/detail";public static final String GET_DETAIL = "/getDetailList";public static final String FIND = "/find";@Autowiredprivate JobDetailService jobDetailService;/*** 查找*/@ResponseBody@RequestMapping(value = FIND, method = RequestMethod.GET)public AjaxPackVo
 find(@RequestParam("id") Long id){AjaxPackVo
 packVo = new AjaxPackVo<>();DetailVo dr = jobDetailService.findVo(id);packVo.setVo(dr);return packVo;}@ResponseBody@RequestMapping(value = GET_DETAIL, method = RequestMethod.POST)public AjaxPackVo
 getDetail(@RequestBody DetailSo detailSo){AjaxPackVo
 detailVoAjaxPackVo = new AjaxPackVo<>();PageList
 detailVoPageList = jobDetailService.getPageList(detailSo);detailVoAjaxPackVo.setPageList(detailVoPageList);return detailVoAjaxPackVo;}

@ResponseStatus:异常上添加注解,从而将其映射为某一个HTTP状态码;

    @ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found")  //将异常映射为HTTP状态404

@ExceptionHandler:方法上添加注解,使其用来处理异常;

    

二.保护Web应用

    使用Spring Security为Web应用提供安全性,保护应用中的信息。

    Spring Security提供了完整的安全性解决方案,能够在Web请求级别和方法调用级别处理身份认证和授权。充分利用了依赖注入和面向切面技术。

    Spring Security从两个角度解决安全性问题:

        (1)使用Servlet规范中的Filter保护Web请求并限制URL级别的访问;

        (2)Spring Security还能够使用Spring AOP保护方法调用--借助于对象代理和使用通知。能够确保只有具备适当权限的用户才能访问安全保护的方法。

    1.Spring Security被分成11个模块:

模块 描述
ACL 支持通过访问控制列表(ACL)为域对象提供安全性
切面(Aspects) 一个很小的模块,当使用Spring Security注解时,会使用基于AspectJ的切面,而不是使用标准的Spring AOP
CAS客户端 提供与Jasig的中心认证服务(Central Authentication Service,CAS)进行集成的功能
配置(Configuration) 包含通过XML和Java配置Spring Security的功能支持
核心(Core) 提供Spring Security基本库
加密(Cryptography) 提供了加密和密码编码的功能
LDAP 支持基于LDAP进行认证
OpenID 支持使用OpenID进行集中式认证
Remoting 提供了对Spring Remoting的支持
标签库(Tag Library) Spring Security的JSP标签库
Web 提供了Spring Security基于Filter的Web安全性支持

    @EnableWebSecurity:该注解将会启用Web安全功能,它必须配置在一个实现了WebSecurityConfigure的bean中;

    @EnableWebMvcSecurity:如果你的应用正好是使用Spring MVC开发的,那么就应该考虑使用该注解代替@EnableWebSecurity;

@Configuration@EnableWebMvcSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter{}

    @EnableWebMvcSecurity注解还配置了一个Spring MVC参数解析解析器(argument resolver),这样的话处理器方法能够通过带有@AuthenticationPrincipal注解的参数获得认证用户的principal(或username)。

        它同时还配置了一个bean,在使用Spring表单绑定标签库来定义表单时,这个bean会自动添加一个隐藏的跨站请求伪造(cross-site request forgery,CSRF)token输入域。

        通过重载WebSecurityConfigureAdapter中的configure()方法配置Web安全性,这个过程中会使用传递进来的参数设置行为。

方法 描述
configure(WebSecurity) 通过重载,配置Spring Security的Filter链
configure(HttpSecurity) 通过重载,配置如何拦截保护请求
configure(AuthenticationManagerBuilder) 通过重载,配置user-detail服务

    

    2.选择查询用户详细信息的服务

        配置用户存储:configure(AuthenticationManagerBuilder)

        AuthenticationManagerBuilder有多个方法可以用来配置SpringSecurity对认证的支持。通过InMemoryAuthentication()方法,可以启用、配置并任意填充基于内存的用户数存储。

方法 描述
accountExpired(boolean) 定义账号是否已经过期
accountLocked(boolean) 定义账号是否已经锁定
and() 用来连接配置
authorities(GrantedAthority...) 授予某个用户一项或多项权限
authorities(List<? extends GrantedAuthority>) 授予某个用户一项或多项权限
authorities(String...) 授予某个用户一项或多项权限
credentialsExpired(boolean) 定义凭证是否已经过期
disable(boolean) 定义账号是否已经被禁用
password(String) 定义用户的密码
roles(String...) 授予某个用户一项或多项角色
@Configuration@EnableWebMvcSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.inMemoryAuthentication()                .withUser("user").password("password").roles("USER").and()                .withUser("admin").password("password").roles("USER", "ADMIN");    }}

    基于数据库表进行认证:jdbcAuthentication();

        使用转码后的密码:passwordEncoder(),该方法可以接受Spring Security中passwordEncoder接口的任意实现。包括了:BCryptPasswordEncoder、NoOpPasswordEncoder和standardPasswordEncoder。

@Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.jdbcAuthentication()                .dataSource(dataSource)                .usersByUsernameQuery(                    "select username, password, true " + "from Spitter where username = ?")                .authoritiesByUsernameQuery("select username, 'ROLE_USER' from Spitter where username=?")                .passwordEncoder(new StandardPasswordEncoder("53cr3t"));    }

    基于LDAP进行认证:ldapAuthentication();

        ldapAuthentication()功能与jdbcAuthentication()类似。ldapAuthentication()可以配置密码比对,密码可指定转码器,可饮用远程的LDAP服务器。

        配置自定义的用户服务:

            提供一个UserDetailsService接口,实现loadUserByUsername方法。

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

    3.拦截请求

        对每个请求进行细粒度安全控制:configure(HttpSecurity);

        定义如何保护路径的配置方法:

方法 作用
access(String) 如果给定的SqEL表达式计算结果为true,就允许访问
anonymous() 允许匿名用户访问
authenticated() 允许认证过的用户访问
denyAll() 无条件拒绝所有访问
fullyAuthenticated() 如果用户是完整认证(不是通过Remember-me功能认证的),就允许访问
hasAnyAuthority(String ...) 如果用户具备给定权限中的某一个,就允许访问
hasAnyRole(String) 如果用户具备给定角色中的某一个,就允许访问
hasAuthority(String) 如果用户具备给定权限,就允许访问
hasIpAddress(String) 如果请求来自给定IP地址,就允许访问
hasRole(String) 如果用户具备给定角色的话,就允许访问
not() 对其他访问方法的结果求反
permitAll() 无条件允许访问
rememberMe() 如果用户是通过Remember-me功能认证的就允许访问

         使用Spring表达式进行安全保护: 

.antMatchers("/spitter/me")    .access("hasRole('ROLE_SPITTER') and hasIpAddress('192.168.1.3')")
安全表达式 计算结果
authentication 用户认证的对象
denyAll 结果始终为false
hasAnyRole(lis of roles) 如果用户被授予了列表中任意的指定角色,结果为true
hasRole(role) 如果用户被授予了指定的角色,结果为true
hasIpAddress(IP Address) 如果请求来自指定IP,结果为true
isAnonymous() 如果当前用户为匿名用户,结果为true
isAuthenticated() 如果当前用户进行了认证的话,结果为true
isFullyAuthenticated() 如果当前用户进行了完整认证的话(不是通过Remember-me功能进行认证),结果为true
isRememberMe() 如果当前用户是通过Remember-me自动认证的,结果为true
permitAll 结果始终为true
principal 用户的principal对象

        强制通道:HTTPS:requiresChannel()

                        HTTP:requiresInsecure()

@Overrideprotected void configure(HttpSecurity http) throws Exception {    http.authorizeRequests()            .antMatchers("/spitter/me").hasRole("SPITTER")            .antMatchers(HttpMethod.POST, "/spittles").hasRole("SPITTER")            .anyRequest().permitAll();        .and()        .requiresChannel()            .antMatchers("/spitter/form").requiresSecure();     --需要HTTPS}

    

    4.用户认证

        启用Remember-me功能:(token中的用户名、密码、过期时间和私钥在写入cookie前都进行了MD5hash)

@Overrideprotected void configure(HttpSecurity http) throws Exception {    http.formLogin()            .loginPage("/login")        .and()        .rememberMe()            .tokenValiditySeconds(2419200)        --指定这个token最多四周内有效            .key("spitterKey")                    --私钥名为spitterKey    ...}

        用户登录表单: 

Remember me