From cd16757f2cd963749850d6f8897381a8b7a849ef Mon Sep 17 00:00:00 2001 From: fengxiang <110431245@qq.com> Date: Thu, 01 Feb 2018 15:09:43 +0800 Subject: [PATCH] 安全模块 --- src/main/java/com/moral/security/CustomCorsFilter.java | 33 + src/main/java/com/moral/config/MvcConfiguration.java | 6 src/main/webapp/js/moralmap.js | 30 src/main/java/com/moral/security/model/token/JwtTokenFactory.java | 93 +++ src/main/java/com/moral/security/auth/login/LoginAwareAuthenticationFailureHandler.java | 53 + src/main/java/com/moral/mapper/MonitorPointMapper.java | 2 src/main/java/com/moral/security/auth/login/LoginProcessingFilter.java | 84 ++ src/main/java/com/moral/security/exceptions/JwtExpiredTokenException.java | 29 src/main/java/com/moral/security/common/ErrorCode.java | 27 src/main/java/com/moral/service/DeviceService.java | 3 src/main/java/com/moral/security/auth/JwtAuthenticationToken.java | 61 + src/main/java/com/moral/security/model/UserContext.java | 58 + src/main/java/com/moral/entity/MonitorPoint.java | 2 src/main/java/com/moral/security/auth/jwt/JwtAuthenticationProvider.java | 68 ++ src/main/resources/mapper/DeviceMapper.xml | 14 src/main/java/com/moral/security/auth/jwt/verifier/TokenVerifier.java | 11 src/main/java/com/moral/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java | 31 + src/main/java/com/moral/security/auth/login/LoginMode.java | 5 src/main/webapp/view/map.jsp | 10 src/main/java/com/moral/entity/Account.java | 11 src/main/resources/system/sysConfig.properties | 3 src/main/java/com/moral/security/auth/login/LoginAwareAuthenticationSuccessHandler.java | 74 ++ src/main/java/com/moral/entity/Role.java | 8 src/main/java/com/moral/security/model/token/JwtToken.java | 5 src/main/java/com/moral/common/util/Crypto.java | 4 src/main/java/com/moral/security/RestAuthenticationEntryPoint.java | 26 src/main/java/com/moral/security/model/token/RawAccessJwtToken.java | 41 + src/main/java/com/moral/security/auth/jwt/verifier/BloomFilterTokenVerifier.java | 18 src/main/java/com/moral/service/impl/DeviceServiceImpl.java | 14 src/main/java/com/moral/mapper/type/AdjustValueTypeHandle.java | 1 src/main/java/com/moral/security/config/PasswordEncoderConfig.java | 20 src/main/java/com/moral/security/endpoint/RefreshTokenEndpoint.java | 82 ++ src/main/java/com/moral/security/model/token/RefreshToken.java | 65 ++ src/main/java/com/moral/security/config/JwtSettings.java | 59 + src/main/java/com/moral/controller/MapController.java | 56 + src/main/java/com/moral/mapper/DeviceMapper.java | 1 src/main/java/com/moral/service/impl/MonitorPointServiceImpl.java | 26 src/main/java/com/moral/security/endpoint/ProfileEndpoint.java | 24 src/main/java/com/moral/service/AccountService.java | 2 src/main/java/com/moral/service/impl/AccountServiceImpl.java | 21 src/main/java/com/moral/security/common/WebUtil.java | 31 + src/main/java/com/moral/security/config/WebSecurityConfig.java | 118 +++ src/main/java/com/moral/security/common/ErrorResponse.java | 52 + src/main/java/com/moral/security/auth/jwt/SkipPathRequestMatcher.java | 37 + /dev/null | 89 -- src/main/java/com/moral/security/auth/login/LoginAuthenticationProvider.java | 74 ++ src/main/java/com/moral/security/auth/jwt/extractor/TokenExtractor.java | 13 src/test/java/com/moral/JavaBeanToJsonOutPrint.java | 13 src/main/java/com/moral/security/auth/login/LoginRequest.java | 40 + src/main/resources/mapper/MonitorPointMapper.xml | 25 src/main/java/com/moral/security/model/Scopes.java | 16 src/main/java/com/moral/security/exceptions/AuthMethodNotSupportedException.java | 17 src/main/java/com/moral/security/model/token/AccessJwtToken.java | 30 src/main/java/com/moral/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java | 64 ++ src/main/java/com/moral/security/exceptions/InvalidJwtToken.java | 12 src/main/resources/application.yml | 12 56 files changed, 1,641 insertions(+), 183 deletions(-) diff --git a/src/main/java/com/moral/common/util/Crypto.java b/src/main/java/com/moral/common/util/Crypto.java index 2a8be1a..14b2996 100644 --- a/src/main/java/com/moral/common/util/Crypto.java +++ b/src/main/java/com/moral/common/util/Crypto.java @@ -29,4 +29,8 @@ } return newstr; } + public static boolean checkpw(String plaintext, String hashed){ + hashed = hashed == null?"":hashed; + return hashed.equals(md5(plaintext)); + } } diff --git a/src/main/java/com/moral/config/MvcConfiguration.java b/src/main/java/com/moral/config/MvcConfiguration.java index 7eff65c..7d63670 100644 --- a/src/main/java/com/moral/config/MvcConfiguration.java +++ b/src/main/java/com/moral/config/MvcConfiguration.java @@ -3,9 +3,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @@ -23,6 +21,4 @@ DefaultServletHandlerConfigurer configurer) { configurer.enable(); } - - } diff --git a/src/main/java/com/moral/controller/MapController.java b/src/main/java/com/moral/controller/MapController.java index c32d4b5..88281a4 100644 --- a/src/main/java/com/moral/controller/MapController.java +++ b/src/main/java/com/moral/controller/MapController.java @@ -2,15 +2,17 @@ import com.alibaba.fastjson.JSONObject; +import com.moral.common.bean.Constants; import com.moral.common.bean.PageBean; +import com.moral.common.bean.PageResult; import com.moral.common.bean.ResultBean; import com.moral.entity.Account; +import com.moral.entity.Device; import com.moral.entity.MapBounds; import com.moral.entity.MonitorPoint; -import com.moral.service.AccountService; -import com.moral.service.AreaService; -import com.moral.service.MonitorPointService; -import com.moral.service.SensorService; +import com.moral.service.*; +import lombok.extern.log4j.Log4j; +import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; @@ -27,6 +29,9 @@ @Controller @RequestMapping("map") public class MapController { + public static Logger log = Logger.getLogger(MapController.class); + @Resource + DeviceService deviceService; @Resource AreaService areaService; @Resource @@ -35,22 +40,33 @@ SensorService sensorService; @Resource MonitorPointService monitorPointService; + @RequestMapping(value = "/main-page", method = RequestMethod.GET) public String map(Model model,@RequestParam("areaCode")int code,@RequestParam("accountId")int accountId){ Account account = accountService.getAccountById(accountId); String regionName = areaService.selectFullNameByCode(code); - Object sensors = sensorService.queryAll(); - JSONObject params = new JSONObject(); - params.put("regionCode",code); - params.put("regionName",regionName); - params.put("accountId", accountId); - params.put("orgId", account.getOrganizationId()); - params.put("sensors", sensors); - String paramsJson = params.toJSONString(); - model.addAttribute("mapParams",paramsJson); - return "map"; + if(account!=null&®ionName!=null){ + Object sensors = sensorService.queryAll(); + JSONObject params = new JSONObject(); + params.put("regionCode",code); + params.put("regionName",regionName); + params.put("accountId", accountId); + params.put("orgId", account.getOrganizationId()); + params.put("sensors", sensors); + String paramsJson = params.toJSONString(); + model.addAttribute("mapParams",paramsJson); + return "map"; + } else { + StringBuilder msg = new StringBuilder(); + msg.append(" param[0] areaCode:"); + msg.append(code); + msg.append(" param[0] accountId:"); + msg.append(accountId); + log.warn(msg); + return "401"; + } } - @RequestMapping(value="/getmonitorpoints",method = RequestMethod.GET) + @RequestMapping(value="/get-monitorpoints",method = RequestMethod.GET) @ResponseBody public ResultBean getMonitorpointList(@RequestParam("orgId")String orgId,MapBounds mapBounds){ ResultBean< List<MonitorPoint>> resultBean = new ResultBean(); @@ -62,4 +78,14 @@ resultBean.setCode(ResultBean.SUCCESS); return resultBean; } + @RequestMapping(value = "get-devices-for-popup",method = RequestMethod.GET) + @ResponseBody + public PageResult getDevicesForPopup( + @RequestParam("orgId")Integer orgId, + String name, + Integer pageSize, + Integer pageNo + ){ + return deviceService.query(orgId,name,pageSize,pageNo); + } } diff --git a/src/main/java/com/moral/entity/Account.java b/src/main/java/com/moral/entity/Account.java index 02eb11e..1498df9 100644 --- a/src/main/java/com/moral/entity/Account.java +++ b/src/main/java/com/moral/entity/Account.java @@ -1,6 +1,8 @@ package com.moral.entity; +import java.util.Arrays; import java.util.Date; +import java.util.List; import javax.persistence.Id; import javax.persistence.Transient; @@ -71,7 +73,14 @@ private Date expireTime; private String userName; - + @Transient + private List<Role> Roles; + // TODO ������������ + public List<Role> getRoles(){ + Role role = new Role(); + role.setName("temp"); + return Arrays.asList(role); + } @Transient private Organization organization; diff --git a/src/main/java/com/moral/entity/MonitorPoint.java b/src/main/java/com/moral/entity/MonitorPoint.java index 3075b6a..5909997 100644 --- a/src/main/java/com/moral/entity/MonitorPoint.java +++ b/src/main/java/com/moral/entity/MonitorPoint.java @@ -96,6 +96,8 @@ */ private String description; @Transient + private Integer state; + @Transient private AreaNames areaNames; @Transient private Organization organization; diff --git a/src/main/java/com/moral/entity/Role.java b/src/main/java/com/moral/entity/Role.java new file mode 100644 index 0000000..4ec205b --- /dev/null +++ b/src/main/java/com/moral/entity/Role.java @@ -0,0 +1,8 @@ +package com.moral.entity; + +import lombok.Data; + +@Data +public class Role { + private String name; +} diff --git a/src/main/java/com/moral/mapper/DeviceMapper.java b/src/main/java/com/moral/mapper/DeviceMapper.java index 0fbc5c4..1eba557 100644 --- a/src/main/java/com/moral/mapper/DeviceMapper.java +++ b/src/main/java/com/moral/mapper/DeviceMapper.java @@ -14,4 +14,5 @@ List<Map<String, Object>> getDeviceStatesByAccount(Map<String, Object> parameters); List<Device> selectWithRelationData(Example example); List<Map<String, Object>> getSensorsByDevice(@Param("mac")String mac); + List<Device> selectByOrgIdAndDevName(@Param("orgId")Integer orgId,@Param("devName")String devName); } \ No newline at end of file diff --git a/src/main/java/com/moral/mapper/MonitorPointMapper.java b/src/main/java/com/moral/mapper/MonitorPointMapper.java index b0addba..f6244bc 100644 --- a/src/main/java/com/moral/mapper/MonitorPointMapper.java +++ b/src/main/java/com/moral/mapper/MonitorPointMapper.java @@ -11,5 +11,5 @@ List<MonitorPoint> selectWithAreaNameByExample(Example example); List<MonitorPoint> getMonitorPointsByAreaName(Map<String, Object> parameters); - List<MonitorPoint> selectWithStateByMap(Map<String, Object> params); + List<MonitorPoint> selectByMap(Map<String, Object> params); } \ No newline at end of file diff --git a/src/main/java/com/moral/mapper/type/AdjustValueTypeHandle.java b/src/main/java/com/moral/mapper/type/AdjustValueTypeHandle.java index 223d3a2..d853e1c 100644 --- a/src/main/java/com/moral/mapper/type/AdjustValueTypeHandle.java +++ b/src/main/java/com/moral/mapper/type/AdjustValueTypeHandle.java @@ -2,6 +2,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.moral.common.json.BooleanValueFilter; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; diff --git a/src/main/java/com/moral/security/AuthorizationServerConfiguration.java b/src/main/java/com/moral/security/AuthorizationServerConfiguration.java deleted file mode 100644 index c0b338f..0000000 --- a/src/main/java/com/moral/security/AuthorizationServerConfiguration.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.moral.security; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; -import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; -import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; -import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; -import org.springframework.security.oauth2.provider.token.TokenStore; - -@Configuration -@EnableAuthorizationServer -public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { - - private static String REALM = "MY_OAUTH_REALM"; - - @Autowired - private UserDetailsService userDetailsService; - - @Autowired - private TokenStore tokenStore; - - @Autowired - private UserApprovalHandler userApprovalHandler; - - @Autowired - @Qualifier("authenticationManagerBean") - private AuthenticationManager authenticationManager; - - @Override - public void configure(ClientDetailsServiceConfigurer clients) throws Exception { - - clients.inMemory() - .withClient("my-trusted-client")//���������ID - .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit") - .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT") - .scopes("read", "write", "trust")//��������������������������� - .secret("secret")//������ - .accessTokenValiditySeconds(1200).//token������������1200��� - refreshTokenValiditySeconds(6000);//������token������������6000��� - } - - @Override - public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { - endpoints.tokenStore(tokenStore) - .userApprovalHandler(userApprovalHandler) - .authenticationManager(authenticationManager) - .userDetailsService(userDetailsService); - } - - @Override - public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { - oauthServer.realm(REALM + "/client"); - } -} \ No newline at end of file diff --git a/src/main/java/com/moral/security/CustomCorsFilter.java b/src/main/java/com/moral/security/CustomCorsFilter.java new file mode 100644 index 0000000..1de04cb --- /dev/null +++ b/src/main/java/com/moral/security/CustomCorsFilter.java @@ -0,0 +1,33 @@ +package com.moral.security; + +import java.util.Arrays; + +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * CustomCorsFilter + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ +public class CustomCorsFilter extends CorsFilter { + + public CustomCorsFilter() { + super(configurationSource()); + } + + private static UrlBasedCorsConfigurationSource configurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin("*"); + config.addAllowedHeader("*"); + config.setMaxAge(36000L); + config.setAllowedMethods(Arrays.asList("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } +} \ No newline at end of file diff --git a/src/main/java/com/moral/security/ResourceServerConfiguration.java b/src/main/java/com/moral/security/ResourceServerConfiguration.java deleted file mode 100644 index edde722..0000000 --- a/src/main/java/com/moral/security/ResourceServerConfiguration.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.moral.security; - -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; - -@Configuration -@EnableResourceServer -public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { - - private static final String RESOURCE_ID = "my_rest_api"; - - @Override - public void configure(ResourceServerSecurityConfigurer resources) { - resources.resourceId(RESOURCE_ID).stateless(false); - } - - @Override - public void configure(HttpSecurity http) throws Exception { - http.anonymous().disable(); - http.requestMatchers() - .antMatchers("/test/**") - .and() - .authorizeRequests() - .antMatchers("/test/**").permitAll() - .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); - - /*http.requestMatchers() - .antMatchers("/screen/**") - .and() - .authorizeRequests() - .antMatchers("/screen/**").permitAll() - .and() - .exceptionHandling() - .accessDeniedHandler(new OAuth2AccessDeniedHandler());*/ - - /*http.requestMatchers() - .antMatchers("/mobile/**") - .and() - .authorizeRequests() - .antMatchers("/mobile/**").permitAll() - .and() - .exceptionHandling() - .accessDeniedHandler(new OAuth2AccessDeniedHandler());*/ - } -} \ No newline at end of file diff --git a/src/main/java/com/moral/security/RestAuthenticationEntryPoint.java b/src/main/java/com/moral/security/RestAuthenticationEntryPoint.java new file mode 100644 index 0000000..b9ceb05 --- /dev/null +++ b/src/main/java/com/moral/security/RestAuthenticationEntryPoint.java @@ -0,0 +1,26 @@ +package com.moral.security; + +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * + * @author vladimir.stankovic + * + * Aug 4, 2016 + */ +@Component +public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) + throws IOException, ServletException { + response.sendError(HttpStatus.UNAUTHORIZED.value(), "Unauthorized"); + } +} diff --git a/src/main/java/com/moral/security/WebMvcConfiguration.java b/src/main/java/com/moral/security/WebMvcConfiguration.java deleted file mode 100644 index c81dd9f..0000000 --- a/src/main/java/com/moral/security/WebMvcConfiguration.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.moral.security; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; - -@Configuration -@EnableWebMvc -public class WebMvcConfiguration extends WebMvcConfigurerAdapter { - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**").allowedOrigins("*") - .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS") - .allowCredentials(false).maxAge(3600); - -// registry.addMapping("/api/**") -// .allowedOrigins("http://192.168.1.97") -// .allowedMethods("GET", "POST") -// .allowCredentials(false).maxAge(3600); - } -} diff --git a/src/main/java/com/moral/security/WebSecurityConfiguration.java b/src/main/java/com/moral/security/WebSecurityConfiguration.java deleted file mode 100644 index d54621f..0000000 --- a/src/main/java/com/moral/security/WebSecurityConfiguration.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.moral.security; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.security.SecurityProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.encoding.Md5PasswordEncoder; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -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; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.approval.ApprovalStore; -import org.springframework.security.oauth2.provider.approval.TokenApprovalStore; -import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler; -import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; -import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; - -@Configuration -@EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) -@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) -public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { - - @Autowired - private ClientDetailsService clientDetailsService; - - @Autowired - private RedisConnectionFactory redisConnection; - - @Autowired - private UserDetailsService userDetailsService; - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication() - .withUser("bill").password("abc123").roles("ADMIN").and() - .withUser("bob").password("abc123").roles("USER"); - - auth.userDetailsService(userDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance()); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.csrf().disable(); //TODO ������������CSRF - http.anonymous().disable() - .authorizeRequests() - .antMatchers("/oauth/token").permitAll(); - } - - @Override - @Bean - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - - - @Bean - public TokenStore tokenStore() { - return new InMemoryTokenStore(); - //return new RedisTokenStore(redisConnection); - } - - @Bean - @Autowired - public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){ - TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler(); - handler.setTokenStore(tokenStore); - handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService)); - handler.setClientDetailsService(clientDetailsService); - return handler; - } - - @Bean - @Autowired - public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception { - TokenApprovalStore store = new TokenApprovalStore(); - store.setTokenStore(tokenStore); - return store; - } -} diff --git a/src/main/java/com/moral/security/auth/JwtAuthenticationToken.java b/src/main/java/com/moral/security/auth/JwtAuthenticationToken.java new file mode 100644 index 0000000..989f525 --- /dev/null +++ b/src/main/java/com/moral/security/auth/JwtAuthenticationToken.java @@ -0,0 +1,61 @@ +package com.moral.security.auth; + +import com.moral.security.model.UserContext; +import com.moral.security.model.token.RawAccessJwtToken; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +/** + * An {@link org.springframework.security.core.Authentication} implementation + * that is designed for simple presentation of JwtToken. + * + * @author vladimir.stankovic + * + * May 23, 2016 + */ +public class JwtAuthenticationToken extends AbstractAuthenticationToken { + private static final long serialVersionUID = 2877954820905567501L; + + private RawAccessJwtToken rawAccessToken; + private UserContext userContext; + + public JwtAuthenticationToken(RawAccessJwtToken unsafeToken) { + super(null); + this.rawAccessToken = unsafeToken; + this.setAuthenticated(false); + } + + public JwtAuthenticationToken(UserContext userContext, Collection<? extends GrantedAuthority> authorities) { + super(authorities); + this.eraseCredentials(); + this.userContext = userContext; + super.setAuthenticated(true); + } + + @Override + public void setAuthenticated(boolean authenticated) { + if (authenticated) { + throw new IllegalArgumentException( + "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); + } + super.setAuthenticated(false); + } + + @Override + public Object getCredentials() { + return rawAccessToken; + } + + @Override + public UserContext getPrincipal() { + return this.userContext; + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + this.rawAccessToken = null; + } +} diff --git a/src/main/java/com/moral/security/auth/jwt/JwtAuthenticationProvider.java b/src/main/java/com/moral/security/auth/jwt/JwtAuthenticationProvider.java new file mode 100644 index 0000000..3afe42a --- /dev/null +++ b/src/main/java/com/moral/security/auth/jwt/JwtAuthenticationProvider.java @@ -0,0 +1,68 @@ +package com.moral.security.auth.jwt; + +import com.moral.security.auth.JwtAuthenticationToken; +import com.moral.security.auth.login.LoginMode; +import com.moral.security.config.JwtSettings; +import com.moral.security.model.UserContext; +import com.moral.security.model.token.JwtToken; +import com.moral.security.model.token.RawAccessJwtToken; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * An {@link AuthenticationProvider} implementation that will use provided + * instance of {@link JwtToken} to perform authentication. + * + * @author vladimir.stankovic + * + * Aug 5, 2016 + */ +@Component +@SuppressWarnings("unchecked") +public class JwtAuthenticationProvider implements AuthenticationProvider { + private final JwtSettings jwtSettings; + + @Autowired + public JwtAuthenticationProvider(JwtSettings jwtSettings) { + this.jwtSettings = jwtSettings; + } + + /** + * ��������������������������������������������������������� + * @param authentication + * @return + * @throws AuthenticationException + */ + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); + + Jws<Claims> jwsClaims = rawAccessToken.parseClaims(jwtSettings.getTokenSigningKey()); + String subject = jwsClaims.getBody().getSubject(); + Integer orgId = Integer.valueOf(jwsClaims.getBody().get("oid").toString()); + LoginMode mode = LoginMode.valueOf(jwsClaims.getBody().get("mode").toString()); + List<String> scopes = jwsClaims.getBody().get("scopes", List.class); + List<GrantedAuthority> authorities = scopes.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + UserContext context = UserContext.create(subject,mode,orgId,authorities); + + return new JwtAuthenticationToken(context, context.getAuthorities()); + } + + @Override + public boolean supports(Class<?> authentication) { + return (JwtAuthenticationToken.class.isAssignableFrom(authentication)); + } +} diff --git a/src/main/java/com/moral/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java b/src/main/java/com/moral/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java new file mode 100644 index 0000000..4fb6f49 --- /dev/null +++ b/src/main/java/com/moral/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java @@ -0,0 +1,64 @@ +package com.moral.security.auth.jwt; + +import com.moral.security.auth.JwtAuthenticationToken; +import com.moral.security.auth.jwt.extractor.TokenExtractor; +import com.moral.security.config.WebSecurityConfig; +import com.moral.security.model.token.RawAccessJwtToken; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Performs validation of provided JWT Token. + * + * @author vladimir.stankovic + * + * Aug 5, 2016 + */ +public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { + private final AuthenticationFailureHandler failureHandler; + private final TokenExtractor tokenExtractor; + + @Autowired + public JwtTokenAuthenticationProcessingFilter(AuthenticationFailureHandler failureHandler, + TokenExtractor tokenExtractor, RequestMatcher matcher) { + super(matcher); + this.failureHandler = failureHandler; + this.tokenExtractor = tokenExtractor; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException, IOException, ServletException { + String tokenPayload = request.getHeader(WebSecurityConfig.AUTHENTICATION_HEADER_NAME); + RawAccessJwtToken token = new RawAccessJwtToken(tokenExtractor.extract(tokenPayload)); + return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token)); + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, + Authentication authResult) throws IOException, ServletException { + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authResult); + SecurityContextHolder.setContext(context); + chain.doFilter(request, response); + } + + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException, ServletException { + SecurityContextHolder.clearContext(); + failureHandler.onAuthenticationFailure(request, response, failed); + } +} diff --git a/src/main/java/com/moral/security/auth/jwt/SkipPathRequestMatcher.java b/src/main/java/com/moral/security/auth/jwt/SkipPathRequestMatcher.java new file mode 100644 index 0000000..7c747d8 --- /dev/null +++ b/src/main/java/com/moral/security/auth/jwt/SkipPathRequestMatcher.java @@ -0,0 +1,37 @@ +package com.moral.security.auth.jwt; + +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.stream.Collectors; + +/** + * SkipPathRequestMatcher + * + * @author vladimir.stankovic + * + * Aug 19, 2016 + */ +public class SkipPathRequestMatcher implements RequestMatcher { + private OrRequestMatcher matchers; + private RequestMatcher processingMatcher; + + public SkipPathRequestMatcher(List<String> pathsToSkip, String processingPath) { + Assert.notNull(pathsToSkip); + List<RequestMatcher> m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList()); + matchers = new OrRequestMatcher(m); + processingMatcher = new AntPathRequestMatcher(processingPath); + } + + @Override + public boolean matches(HttpServletRequest request) { + if (matchers.matches(request)) { + return false; + } + return processingMatcher.matches(request) ? true : false; + } +} diff --git a/src/main/java/com/moral/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java b/src/main/java/com/moral/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java new file mode 100644 index 0000000..bf2fc27 --- /dev/null +++ b/src/main/java/com/moral/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java @@ -0,0 +1,31 @@ +package com.moral.security.auth.jwt.extractor; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.stereotype.Component; + +/** + * An implementation of {@link TokenExtractor} extracts token from + * Authorization: Bearer scheme. + * + * @author vladimir.stankovic + * + * Aug 5, 2016 + */ +@Component +public class JwtHeaderTokenExtractor implements TokenExtractor { + public static String HEADER_PREFIX = "Bearer "; + + @Override + public String extract(String header) { + if (StringUtils.isBlank(header)) { + throw new AuthenticationServiceException("Authorization header cannot be blank!"); + } + + if (header.length() < HEADER_PREFIX.length()) { + throw new AuthenticationServiceException("Invalid authorization header size."); + } + + return header.substring(HEADER_PREFIX.length(), header.length()); + } +} diff --git a/src/main/java/com/moral/security/auth/jwt/extractor/TokenExtractor.java b/src/main/java/com/moral/security/auth/jwt/extractor/TokenExtractor.java new file mode 100644 index 0000000..79cf7d4 --- /dev/null +++ b/src/main/java/com/moral/security/auth/jwt/extractor/TokenExtractor.java @@ -0,0 +1,13 @@ +package com.moral.security.auth.jwt.extractor; + +/** + * Implementations of this interface should always return raw base-64 encoded + * representation of JWT Token. + * + * @author vladimir.stankovic + * + * Aug 5, 2016 + */ +public interface TokenExtractor { + public String extract(String payload); +} diff --git a/src/main/java/com/moral/security/auth/jwt/verifier/BloomFilterTokenVerifier.java b/src/main/java/com/moral/security/auth/jwt/verifier/BloomFilterTokenVerifier.java new file mode 100644 index 0000000..d2ed3c2 --- /dev/null +++ b/src/main/java/com/moral/security/auth/jwt/verifier/BloomFilterTokenVerifier.java @@ -0,0 +1,18 @@ +package com.moral.security.auth.jwt.verifier; + +import org.springframework.stereotype.Component; + +/** + * BloomFilterTokenVerifier + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +@Component +public class BloomFilterTokenVerifier implements TokenVerifier { + @Override + public boolean verify(String jti) { + return true; + } +} diff --git a/src/main/java/com/moral/security/auth/jwt/verifier/TokenVerifier.java b/src/main/java/com/moral/security/auth/jwt/verifier/TokenVerifier.java new file mode 100644 index 0000000..0e6d014 --- /dev/null +++ b/src/main/java/com/moral/security/auth/jwt/verifier/TokenVerifier.java @@ -0,0 +1,11 @@ +package com.moral.security.auth.jwt.verifier; + +/** + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +public interface TokenVerifier { + public boolean verify(String jti); +} diff --git a/src/main/java/com/moral/security/auth/login/LoginAuthenticationProvider.java b/src/main/java/com/moral/security/auth/login/LoginAuthenticationProvider.java new file mode 100644 index 0000000..307d880 --- /dev/null +++ b/src/main/java/com/moral/security/auth/login/LoginAuthenticationProvider.java @@ -0,0 +1,74 @@ +package com.moral.security.auth.login; + +import com.moral.entity.Account; +import com.moral.security.model.UserContext; +import com.moral.service.AccountService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author vladimir.stankovic + * <p> + * Aug 3, 2016 + */ +@Component +public class LoginAuthenticationProvider implements AuthenticationProvider { + private final BCryptPasswordEncoder encoder; + private final AccountService accountService; + + @Autowired + public LoginAuthenticationProvider(final AccountService accountService, final BCryptPasswordEncoder encoder) { + this.accountService = accountService; + this.encoder = encoder; + } + + /** + * ������������������������ + * + * @param authentication + * @return + * @throws AuthenticationException + */ + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Assert.notNull(authentication, "No authentication data provided"); + + String accountName = (String) authentication.getPrincipal(); + String password = (String) authentication.getCredentials(); + LoginMode mode = (LoginMode) authentication.getDetails(); + Account account = accountService.queryAccountByName(accountName).orElseThrow(() -> new UsernameNotFoundException("User not found: " + accountName)); + if (!encoder.matches(password, account.getPassword())) { + throw new BadCredentialsException("Authentication Failed. Username or Password not valid."); + } + + if (account.getRoles() == null) { + throw new InsufficientAuthenticationException("User has no roles assigned"); + } + List<GrantedAuthority> authorities = account.getRoles().stream() + .map(authority -> new SimpleGrantedAuthority(authority.getName())) + .collect(Collectors.toList()); + + UserContext userContext = UserContext.create(account.getAccountName(),mode,account.getOrganizationId(),authorities); + + return new UsernamePasswordAuthenticationToken(userContext, null, userContext.getAuthorities()); + } + + @Override + public boolean supports(Class<?> authentication) { + return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); + } +} diff --git a/src/main/java/com/moral/security/auth/login/LoginAwareAuthenticationFailureHandler.java b/src/main/java/com/moral/security/auth/login/LoginAwareAuthenticationFailureHandler.java new file mode 100644 index 0000000..60c94ae --- /dev/null +++ b/src/main/java/com/moral/security/auth/login/LoginAwareAuthenticationFailureHandler.java @@ -0,0 +1,53 @@ +package com.moral.security.auth.login; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moral.security.common.ErrorCode; +import com.moral.security.common.ErrorResponse; +import com.moral.security.exceptions.AuthMethodNotSupportedException; +import com.moral.security.exceptions.JwtExpiredTokenException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ +@Component +public class LoginAwareAuthenticationFailureHandler implements AuthenticationFailureHandler { + private final ObjectMapper mapper; + + @Autowired + public LoginAwareAuthenticationFailureHandler(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException e) throws IOException, ServletException { + + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + if (e instanceof BadCredentialsException) { + mapper.writeValue(response.getWriter(), ErrorResponse.of("Invalid username or password", ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); + } else if (e instanceof JwtExpiredTokenException) { + mapper.writeValue(response.getWriter(), ErrorResponse.of("Token has expired", ErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)); + } else if (e instanceof AuthMethodNotSupportedException) { + mapper.writeValue(response.getWriter(), ErrorResponse.of(e.getMessage(), ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); + } + + mapper.writeValue(response.getWriter(), ErrorResponse.of("Authentication failed", ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); + } +} diff --git a/src/main/java/com/moral/security/auth/login/LoginAwareAuthenticationSuccessHandler.java b/src/main/java/com/moral/security/auth/login/LoginAwareAuthenticationSuccessHandler.java new file mode 100644 index 0000000..02c6d29 --- /dev/null +++ b/src/main/java/com/moral/security/auth/login/LoginAwareAuthenticationSuccessHandler.java @@ -0,0 +1,74 @@ +package com.moral.security.auth.login; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moral.security.model.UserContext; +import com.moral.security.model.token.JwtToken; +import com.moral.security.model.token.JwtTokenFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.WebAttributes; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * AjaxAwareAuthenticationSuccessHandler + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ +@Component +public class LoginAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + private final ObjectMapper mapper; + private final JwtTokenFactory tokenFactory; + + @Autowired + public LoginAwareAuthenticationSuccessHandler(final ObjectMapper mapper, final JwtTokenFactory tokenFactory) { + this.mapper = mapper; + this.tokenFactory = tokenFactory; + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + UserContext userContext = (UserContext) authentication.getPrincipal(); + + JwtToken accessToken = tokenFactory.createAccessJwtToken(userContext); + JwtToken refreshToken = tokenFactory.createRefreshToken(userContext); + + Map<String, String> tokenMap = new HashMap<String, String>(); + tokenMap.put("token", accessToken.getToken()); + tokenMap.put("refreshToken", refreshToken.getToken()); + + response.setStatus(HttpStatus.OK.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + mapper.writeValue(response.getWriter(), tokenMap); + + clearAuthenticationAttributes(request); + } + + /** + * Removes temporary authentication-related data which may have been stored + * in the session during the authentication process.. + * + */ + protected final void clearAuthenticationAttributes(HttpServletRequest request) { + HttpSession session = request.getSession(false); + + if (session == null) { + return; + } + + session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); + } +} diff --git a/src/main/java/com/moral/security/auth/login/LoginMode.java b/src/main/java/com/moral/security/auth/login/LoginMode.java new file mode 100644 index 0000000..4681349 --- /dev/null +++ b/src/main/java/com/moral/security/auth/login/LoginMode.java @@ -0,0 +1,5 @@ +package com.moral.security.auth.login; + +public enum LoginMode { + Web,Screen,Andriod,Apple +} diff --git a/src/main/java/com/moral/security/auth/login/LoginProcessingFilter.java b/src/main/java/com/moral/security/auth/login/LoginProcessingFilter.java new file mode 100644 index 0000000..6f411ea --- /dev/null +++ b/src/main/java/com/moral/security/auth/login/LoginProcessingFilter.java @@ -0,0 +1,84 @@ +package com.moral.security.auth.login; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moral.security.common.WebUtil; +import com.moral.security.exceptions.AuthMethodNotSupportedException; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * AjaxLoginProcessingFilter + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ +public class LoginProcessingFilter extends AbstractAuthenticationProcessingFilter { + private static Logger logger = LoggerFactory.getLogger(LoginProcessingFilter.class); + + private final AuthenticationSuccessHandler successHandler; + private final AuthenticationFailureHandler failureHandler; + + private final ObjectMapper objectMapper; + + public LoginProcessingFilter(String defaultProcessUrl, AuthenticationSuccessHandler successHandler, + AuthenticationFailureHandler failureHandler, ObjectMapper mapper) { + super(defaultProcessUrl); + this.successHandler = successHandler; + this.failureHandler = failureHandler; + this.objectMapper = mapper; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException, IOException, ServletException { + if (!HttpMethod.POST.name().equals(request.getMethod())) { + if(logger.isDebugEnabled()) { + logger.debug("Authentication method not supported. Request method: " + request.getMethod()); + } + throw new AuthMethodNotSupportedException("Authentication method not supported"); + } + + LoginRequest loginRequest = objectMapper.readValue(request.getReader(), LoginRequest.class); + + if (StringUtils.isBlank(loginRequest.getUsername()) + || StringUtils.isBlank(loginRequest.getPassword()) + || loginRequest.getMode() == null) { + throw new AuthenticationServiceException("Username or Password not provided"); + } + + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); + // ������������������������������������������������ + token.setDetails(loginRequest.getMode()); + return this.getAuthenticationManager().authenticate(token); + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, + Authentication authResult) throws IOException, ServletException { + successHandler.onAuthenticationSuccess(request, response, authResult); + } + + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException, ServletException { + SecurityContextHolder.clearContext(); + failureHandler.onAuthenticationFailure(request, response, failed); + } +} diff --git a/src/main/java/com/moral/security/auth/login/LoginRequest.java b/src/main/java/com/moral/security/auth/login/LoginRequest.java new file mode 100644 index 0000000..be34bf8 --- /dev/null +++ b/src/main/java/com/moral/security/auth/login/LoginRequest.java @@ -0,0 +1,40 @@ +package com.moral.security.auth.login; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Model intended to be used for AJAX based authentication. + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ + +public class LoginRequest { + private String username; + private String password; + + public LoginMode getMode() { + return mode; + } + + private LoginMode mode; + + @JsonCreator + public LoginRequest(@JsonProperty("username") String username, + @JsonProperty("password") String password, + @JsonProperty("mode") LoginMode mode) { + this.username = username; + this.password = password; + this.mode = mode; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/com/moral/security/common/ErrorCode.java b/src/main/java/com/moral/security/common/ErrorCode.java new file mode 100644 index 0000000..db6750d --- /dev/null +++ b/src/main/java/com/moral/security/common/ErrorCode.java @@ -0,0 +1,27 @@ +package com.moral.security.common; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Enumeration of REST Error types. + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ +public enum ErrorCode { + GLOBAL(2), + + AUTHENTICATION(10), JWT_TOKEN_EXPIRED(11); + + private int errorCode; + + private ErrorCode(int errorCode) { + this.errorCode = errorCode; + } + + @JsonValue + public int getErrorCode() { + return errorCode; + } +} diff --git a/src/main/java/com/moral/security/common/ErrorResponse.java b/src/main/java/com/moral/security/common/ErrorResponse.java new file mode 100644 index 0000000..ec30c90 --- /dev/null +++ b/src/main/java/com/moral/security/common/ErrorResponse.java @@ -0,0 +1,52 @@ +package com.moral.security.common; + +import org.springframework.http.HttpStatus; + +import java.util.Date; + +/** + * Error model for interacting with client. + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ +public class ErrorResponse { + // HTTP Response Status Code + private final HttpStatus status; + + // General Error message + private final String message; + + // Error code + private final ErrorCode errorCode; + + private final Date timestamp; + + protected ErrorResponse(final String message, final ErrorCode errorCode, HttpStatus status) { + this.message = message; + this.errorCode = errorCode; + this.status = status; + this.timestamp = new Date(); + } + + public static ErrorResponse of(final String message, final ErrorCode errorCode, HttpStatus status) { + return new ErrorResponse(message, errorCode, status); + } + + public Integer getStatus() { + return status.value(); + } + + public String getMessage() { + return message; + } + + public ErrorCode getErrorCode() { + return errorCode; + } + + public Date getTimestamp() { + return timestamp; + } +} diff --git a/src/main/java/com/moral/security/common/WebUtil.java b/src/main/java/com/moral/security/common/WebUtil.java new file mode 100644 index 0000000..2a1e822 --- /dev/null +++ b/src/main/java/com/moral/security/common/WebUtil.java @@ -0,0 +1,31 @@ +package com.moral.security.common; + +import org.springframework.security.web.savedrequest.SavedRequest; + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ +public class WebUtil { + private static final String XML_HTTP_REQUEST = "XMLHttpRequest"; + private static final String X_REQUESTED_WITH = "X-Requested-With"; + + private static final String CONTENT_TYPE = "Content-type"; + private static final String CONTENT_TYPE_JSON = "application/json"; + + public static boolean isAjax(HttpServletRequest request) { + return XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH)); + } + + public static boolean isAjax(SavedRequest request) { + return request.getHeaderValues(X_REQUESTED_WITH).contains(XML_HTTP_REQUEST); + } + + public static boolean isContentTypeJson(SavedRequest request) { + return request.getHeaderValues(CONTENT_TYPE).contains(CONTENT_TYPE_JSON); + } +} diff --git a/src/main/java/com/moral/security/config/JwtSettings.java b/src/main/java/com/moral/security/config/JwtSettings.java new file mode 100644 index 0000000..fc96aac --- /dev/null +++ b/src/main/java/com/moral/security/config/JwtSettings.java @@ -0,0 +1,59 @@ +package com.moral.security.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "moral.security.jwt") +public class JwtSettings { + /** + * {@link JwtToken} will expire after this time. + */ + private Integer tokenExpirationTime; + + /** + * Token issuer. + */ + private String tokenIssuer; + + /** + * Key is used to sign {@link JwtToken}. + */ + private String tokenSigningKey; + + /** + * {@link JwtToken} can be refreshed during this timeframe. + */ + private Integer refreshTokenExpTime; + + public Integer getRefreshTokenExpTime() { + return refreshTokenExpTime; + } + + public void setRefreshTokenExpTime(Integer refreshTokenExpTime) { + this.refreshTokenExpTime = refreshTokenExpTime; + } + + public Integer getTokenExpirationTime() { + return tokenExpirationTime; + } + + public void setTokenExpirationTime(Integer tokenExpirationTime) { + this.tokenExpirationTime = tokenExpirationTime; + } + + public String getTokenIssuer() { + return tokenIssuer; + } + public void setTokenIssuer(String tokenIssuer) { + this.tokenIssuer = tokenIssuer; + } + + public String getTokenSigningKey() { + return tokenSigningKey; + } + + public void setTokenSigningKey(String tokenSigningKey) { + this.tokenSigningKey = tokenSigningKey; + } +} diff --git a/src/main/java/com/moral/security/config/PasswordEncoderConfig.java b/src/main/java/com/moral/security/config/PasswordEncoderConfig.java new file mode 100644 index 0000000..12de433 --- /dev/null +++ b/src/main/java/com/moral/security/config/PasswordEncoderConfig.java @@ -0,0 +1,20 @@ +package com.moral.security.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +/** + * PasswordEncoderConfig + * + * @author vladimir.stankovic + * + * Dec 27, 2016 + */ +@Configuration +public class PasswordEncoderConfig { + @Bean + protected BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/moral/security/config/WebSecurityConfig.java b/src/main/java/com/moral/security/config/WebSecurityConfig.java new file mode 100644 index 0000000..d6b75e9 --- /dev/null +++ b/src/main/java/com/moral/security/config/WebSecurityConfig.java @@ -0,0 +1,118 @@ +package com.moral.security.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moral.security.CustomCorsFilter; +import com.moral.security.RestAuthenticationEntryPoint; +import com.moral.security.auth.login.LoginAuthenticationProvider; +import com.moral.security.auth.login.LoginProcessingFilter; +import com.moral.security.auth.jwt.JwtAuthenticationProvider; +import com.moral.security.auth.jwt.JwtTokenAuthenticationProcessingFilter; +import com.moral.security.auth.jwt.SkipPathRequestMatcher; +import com.moral.security.auth.jwt.extractor.TokenExtractor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +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; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import java.util.Arrays; +import java.util.List; + +/** + * WebSecurityConfig + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + public static final String AUTHENTICATION_HEADER_NAME = "X-Authorization"; + public static final String AUTHENTICATION_URL = "/auth/login"; + public static final String REFRESH_TOKEN_URL = "/auth/token"; + public static final String API_ROOT_URL = "/*/**"; + + @Autowired + private RestAuthenticationEntryPoint authenticationEntryPoint; + @Autowired + private AuthenticationSuccessHandler successHandler; + @Autowired + private AuthenticationFailureHandler failureHandler; + @Autowired + private LoginAuthenticationProvider ajaxAuthenticationProvider; + @Autowired + private JwtAuthenticationProvider jwtAuthenticationProvider; + + @Autowired + private TokenExtractor tokenExtractor; + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private ObjectMapper objectMapper; + + protected LoginProcessingFilter buildLoginProcessingFilter(String loginEntryPoint) throws Exception { + LoginProcessingFilter filter = new LoginProcessingFilter(loginEntryPoint, successHandler, failureHandler, objectMapper); + filter.setAuthenticationManager(this.authenticationManager); + return filter; + } + + protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter(List<String> pathsToSkip, String pattern) throws Exception { + SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, pattern); + JwtTokenAuthenticationProcessingFilter filter + = new JwtTokenAuthenticationProcessingFilter(failureHandler, tokenExtractor, matcher); + filter.setAuthenticationManager(this.authenticationManager); + return filter; + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) { + auth.authenticationProvider(ajaxAuthenticationProvider); + auth.authenticationProvider(jwtAuthenticationProvider); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + List<String> permitAllEndpointList = Arrays.asList( + AUTHENTICATION_URL, + REFRESH_TOKEN_URL + ); + + http + .csrf().disable() // We don't need CSRF for JWT based authentication + .exceptionHandling() + .authenticationEntryPoint(this.authenticationEntryPoint) + + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + .and() + .authorizeRequests() + .antMatchers(permitAllEndpointList.toArray(new String[permitAllEndpointList.size()])) + .permitAll() + .and() + .authorizeRequests() + .antMatchers(API_ROOT_URL).authenticated() // Protected API End-points + .and() + .addFilterBefore(new CustomCorsFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildLoginProcessingFilter(AUTHENTICATION_URL), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(permitAllEndpointList, + API_ROOT_URL), UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/src/main/java/com/moral/security/endpoint/ProfileEndpoint.java b/src/main/java/com/moral/security/endpoint/ProfileEndpoint.java new file mode 100644 index 0000000..9349580 --- /dev/null +++ b/src/main/java/com/moral/security/endpoint/ProfileEndpoint.java @@ -0,0 +1,24 @@ +package com.moral.security.endpoint; + +import com.moral.security.auth.JwtAuthenticationToken; +import com.moral.security.model.UserContext; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * End-point for retrieving logged-in user details. + * + * @author vladimir.stankovic + * + * Aug 4, 2016 + */ +@RestController +public class ProfileEndpoint { + @RequestMapping(value="/user-context", method=RequestMethod.GET) + public @ResponseBody + UserContext get(JwtAuthenticationToken token) { + return token.getPrincipal(); + } +} diff --git a/src/main/java/com/moral/security/endpoint/RefreshTokenEndpoint.java b/src/main/java/com/moral/security/endpoint/RefreshTokenEndpoint.java new file mode 100644 index 0000000..f965e0f --- /dev/null +++ b/src/main/java/com/moral/security/endpoint/RefreshTokenEndpoint.java @@ -0,0 +1,82 @@ +package com.moral.security.endpoint; + +import com.moral.entity.Account; +import com.moral.security.auth.JwtAuthenticationToken; +import com.moral.security.auth.login.LoginMode; +import com.moral.security.model.token.JwtTokenFactory; +import com.moral.security.auth.jwt.extractor.TokenExtractor; +import com.moral.security.auth.jwt.verifier.TokenVerifier; +import com.moral.security.config.JwtSettings; +import com.moral.security.config.WebSecurityConfig; +import com.moral.security.exceptions.InvalidJwtToken; +import com.moral.security.model.UserContext; +import com.moral.security.model.token.JwtToken; +import com.moral.security.model.token.RawAccessJwtToken; +import com.moral.security.model.token.RefreshToken; +import com.moral.service.AccountService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +/** + * RefreshTokenEndpoint + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +@RestController +public class RefreshTokenEndpoint { + @Autowired + private JwtTokenFactory tokenFactory; + @Autowired + private JwtSettings jwtSettings; + @Autowired + private AccountService accountService; + @Autowired + private TokenVerifier tokenVerifier; + @Autowired + @Qualifier("jwtHeaderTokenExtractor") private TokenExtractor tokenExtractor; + + @RequestMapping(value="/auth/token", method= RequestMethod.GET, produces={ MediaType.APPLICATION_JSON_VALUE }) + public @ResponseBody + JwtToken refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + String tokenPayload = tokenExtractor.extract(request.getHeader(WebSecurityConfig.AUTHENTICATION_HEADER_NAME)); + + RawAccessJwtToken rawToken = new RawAccessJwtToken(tokenPayload); + RefreshToken refreshToken = RefreshToken.create(rawToken, jwtSettings.getTokenSigningKey()).orElseThrow(() -> new InvalidJwtToken()); + + String jti = refreshToken.getJti(); + if (!tokenVerifier.verify(jti)) { + throw new InvalidJwtToken(); + } + String subject = refreshToken.getSubject(); + // ���refresh token��� ������������������ + LoginMode mode = LoginMode.valueOf(refreshToken.getClaims().getBody().get("mode").toString()); + Account account = accountService.queryAccountByName(subject).orElseThrow(() -> new UsernameNotFoundException("User not found: " + subject)); + + if (account.getRoles() == null) throw new InsufficientAuthenticationException("User has no roles assigned"); + List<GrantedAuthority> authorities = account.getRoles().stream() + .map(authority -> new SimpleGrantedAuthority(authority.getName())) + .collect(Collectors.toList()); + + UserContext userContext = UserContext.create(account.getAccountName(),mode,account.getOrganizationId(),authorities); + + return tokenFactory.createAccessJwtToken(userContext); + } +} diff --git a/src/main/java/com/moral/security/exceptions/AuthMethodNotSupportedException.java b/src/main/java/com/moral/security/exceptions/AuthMethodNotSupportedException.java new file mode 100644 index 0000000..9b6db5c --- /dev/null +++ b/src/main/java/com/moral/security/exceptions/AuthMethodNotSupportedException.java @@ -0,0 +1,17 @@ +package com.moral.security.exceptions; + +import org.springframework.security.authentication.AuthenticationServiceException; + +/** + * + * @author vladimir.stankovic + * + * Aug 4, 2016 + */ +public class AuthMethodNotSupportedException extends AuthenticationServiceException { + private static final long serialVersionUID = 3705043083010304496L; + + public AuthMethodNotSupportedException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/moral/security/exceptions/InvalidJwtToken.java b/src/main/java/com/moral/security/exceptions/InvalidJwtToken.java new file mode 100644 index 0000000..d4c64ec --- /dev/null +++ b/src/main/java/com/moral/security/exceptions/InvalidJwtToken.java @@ -0,0 +1,12 @@ +package com.moral.security.exceptions; + +/** + * JwtTokenNotValid + * + * @author vladimir.stankovic + * + * Aug 17, 2016 + */ +public class InvalidJwtToken extends RuntimeException { + private static final long serialVersionUID = -294671188037098603L; +} diff --git a/src/main/java/com/moral/security/exceptions/JwtExpiredTokenException.java b/src/main/java/com/moral/security/exceptions/JwtExpiredTokenException.java new file mode 100644 index 0000000..0259570 --- /dev/null +++ b/src/main/java/com/moral/security/exceptions/JwtExpiredTokenException.java @@ -0,0 +1,29 @@ +package com.moral.security.exceptions; + +import com.moral.security.model.token.JwtToken; +import org.springframework.security.core.AuthenticationException; + +/** + * + * @author vladimir.stankovic + * + * Aug 3, 2016 + */ +public class JwtExpiredTokenException extends AuthenticationException { + private static final long serialVersionUID = -5959543783324224864L; + + private JwtToken token; + + public JwtExpiredTokenException(String msg) { + super(msg); + } + + public JwtExpiredTokenException(JwtToken token, String msg, Throwable t) { + super(msg, t); + this.token = token; + } + + public String token() { + return this.token.getToken(); + } +} diff --git a/src/main/java/com/moral/security/model/Scopes.java b/src/main/java/com/moral/security/model/Scopes.java new file mode 100644 index 0000000..b6dbc24 --- /dev/null +++ b/src/main/java/com/moral/security/model/Scopes.java @@ -0,0 +1,16 @@ +package com.moral.security.model; + +/** + * Scopes + * + * @author vladimir.stankovic + * + * Aug 18, 2016 + */ +public enum Scopes { + REFRESH_TOKEN; + + public String authority() { + return "ROLE_" + this.name(); + } +} diff --git a/src/main/java/com/moral/security/model/UserContext.java b/src/main/java/com/moral/security/model/UserContext.java new file mode 100644 index 0000000..cc9e8b1 --- /dev/null +++ b/src/main/java/com/moral/security/model/UserContext.java @@ -0,0 +1,58 @@ +package com.moral.security.model; + +import com.moral.security.auth.login.LoginMode; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.GrantedAuthority; + +import java.util.List; + +/** + * + * @author vladimir.stankovic + * + * Aug 4, 2016 + */ +public class UserContext { + private final String username; + + public LoginMode getMode() { + return mode; + } + + private final LoginMode mode; + + public Integer getOrganizationId() { + return organizationId; + } + + private final Integer organizationId; + private final List<GrantedAuthority> authorities; + + private UserContext(String username, LoginMode mode, Integer organizationId, List<GrantedAuthority> authorities) { + this.username = username; + this.mode = mode; + this.organizationId = organizationId; + this.authorities = authorities; + } + + /** + * + * @param username ��������� + * @param mode ������������ + * @param organizationId ������������ + * @param authorities ������������ + * @return + */ + public static UserContext create(String username,LoginMode mode,Integer organizationId, List<GrantedAuthority> authorities) { + if (StringUtils.isBlank(username)) throw new IllegalArgumentException("Username is blank: " + username); + return new UserContext(username, mode, organizationId, authorities); + } + + public String getUsername() { + return username; + } + + public List<GrantedAuthority> getAuthorities() { + return authorities; + } +} diff --git a/src/main/java/com/moral/security/model/token/AccessJwtToken.java b/src/main/java/com/moral/security/model/token/AccessJwtToken.java new file mode 100644 index 0000000..28f2d96 --- /dev/null +++ b/src/main/java/com/moral/security/model/token/AccessJwtToken.java @@ -0,0 +1,30 @@ +package com.moral.security.model.token; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.jsonwebtoken.Claims; + +/** + * Raw representation of JWT Token. + * + * @author vladimir.stankovic + * + * May 31, 2016 + */ +public final class AccessJwtToken implements JwtToken { + private final String rawToken; + @JsonIgnore + private Claims claims; + + protected AccessJwtToken(final String token, Claims claims) { + this.rawToken = token; + this.claims = claims; + } + + public String getToken() { + return this.rawToken; + } + + public Claims getClaims() { + return claims; + } +} diff --git a/src/main/java/com/moral/security/model/token/JwtToken.java b/src/main/java/com/moral/security/model/token/JwtToken.java new file mode 100644 index 0000000..dc47498 --- /dev/null +++ b/src/main/java/com/moral/security/model/token/JwtToken.java @@ -0,0 +1,5 @@ +package com.moral.security.model.token; + +public interface JwtToken { + String getToken(); +} diff --git a/src/main/java/com/moral/security/model/token/JwtTokenFactory.java b/src/main/java/com/moral/security/model/token/JwtTokenFactory.java new file mode 100644 index 0000000..6db2228 --- /dev/null +++ b/src/main/java/com/moral/security/model/token/JwtTokenFactory.java @@ -0,0 +1,93 @@ +package com.moral.security.model.token; + +import com.moral.security.config.JwtSettings; +import com.moral.security.model.Scopes; +import com.moral.security.model.UserContext; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Date; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * Factory class that should be always used to create {@link JwtToken}. + * + * @author vladimir.stankovic + * + * May 31, 2016 + */ +@Component +public class JwtTokenFactory { + private final JwtSettings settings; + + @Autowired + public JwtTokenFactory(JwtSettings settings) { + this.settings = settings; + } + + /** + * Factory method for issuing new JWT Tokens. + * + * @param userContext + * @return + */ + public AccessJwtToken createAccessJwtToken(UserContext userContext) { + if (StringUtils.isBlank(userContext.getUsername())) + throw new IllegalArgumentException("Cannot create JWT Token without username"); + + if (userContext.getAuthorities() == null || userContext.getAuthorities().isEmpty()) + throw new IllegalArgumentException("User doesn't have any privileges"); + + Claims claims = Jwts.claims().setSubject(userContext.getUsername()); + claims.put("oid",userContext.getOrganizationId()); + claims.put("mode",userContext.getMode()); + claims.put("scopes", userContext.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList())); + + LocalDateTime currentTime = LocalDateTime.now(); + + String token = Jwts.builder() + .setClaims(claims) + .setIssuer(settings.getTokenIssuer()) + .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())) + .setExpiration(Date.from(currentTime + .plusMinutes(settings.getTokenExpirationTime()) + .atZone(ZoneId.systemDefault()).toInstant())) + .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey()) + .compact(); + + return new AccessJwtToken(token, claims); + } + + public JwtToken createRefreshToken(UserContext userContext) { + if (StringUtils.isBlank(userContext.getUsername())) { + throw new IllegalArgumentException("Cannot create JWT Token without username"); + } + + LocalDateTime currentTime = LocalDateTime.now(); + + Claims claims = Jwts.claims().setSubject(userContext.getUsername()); + claims.put("mode",userContext.getMode()); +// claims.put("scopes", Arrays.asList(Scopes.REFRESH_TOKEN.authority())); + + String token = Jwts.builder() + .setClaims(claims) + .setIssuer(settings.getTokenIssuer()) + .setId(UUID.randomUUID().toString()) + .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())) + .setExpiration(Date.from(currentTime + .plusMinutes(settings.getRefreshTokenExpTime()) + .atZone(ZoneId.systemDefault()).toInstant())) + .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey()) + .compact(); + + return new AccessJwtToken(token, claims); + } +} diff --git a/src/main/java/com/moral/security/model/token/RawAccessJwtToken.java b/src/main/java/com/moral/security/model/token/RawAccessJwtToken.java new file mode 100644 index 0000000..b1d2bff --- /dev/null +++ b/src/main/java/com/moral/security/model/token/RawAccessJwtToken.java @@ -0,0 +1,41 @@ +package com.moral.security.model.token; + +import com.moral.security.exceptions.JwtExpiredTokenException; +import io.jsonwebtoken.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.BadCredentialsException; + +public class RawAccessJwtToken implements JwtToken { + private static Logger logger = LoggerFactory.getLogger(RawAccessJwtToken.class); + + private String token; + + public RawAccessJwtToken(String token) { + this.token = token; + } + + /** + * Parses and validates JWT Token signature. + * + * @throws BadCredentialsException + * @throws JwtExpiredTokenException + * + */ + public Jws<Claims> parseClaims(String signingKey) { + try { + return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); + } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { + logger.error("Invalid JWT Token", ex); + throw new BadCredentialsException("Invalid JWT token: ", ex); + } catch (ExpiredJwtException expiredEx) { + logger.info("JWT Token is expired", expiredEx); + throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx); + } + } + + @Override + public String getToken() { + return token; + } +} diff --git a/src/main/java/com/moral/security/model/token/RefreshToken.java b/src/main/java/com/moral/security/model/token/RefreshToken.java new file mode 100644 index 0000000..0f67cd0 --- /dev/null +++ b/src/main/java/com/moral/security/model/token/RefreshToken.java @@ -0,0 +1,65 @@ +package com.moral.security.model.token; + +import com.moral.security.model.Scopes; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import org.springframework.security.authentication.BadCredentialsException; + +import java.util.List; +import java.util.Optional; + +/** + * RefreshToken + * + * @author vladimir.stankovic + * + * Aug 19, 2016 + */ +@SuppressWarnings("unchecked") +public class RefreshToken implements JwtToken { + private Jws<Claims> claims; + + private RefreshToken(Jws<Claims> claims) { + this.claims = claims; + } + + /** + * Creates and validates Refresh token + * + * @param token + * @param signingKey + * + * @throws BadCredentialsException + * @throws JwtExpiredTokenException + * + * @return + */ + public static Optional<RefreshToken> create(RawAccessJwtToken token, String signingKey) { + Jws<Claims> claims = token.parseClaims(signingKey); + + List<String> scopes = claims.getBody().get("scopes", List.class); + if (scopes == null || scopes.isEmpty() + || !scopes.stream().filter(scope -> Scopes.REFRESH_TOKEN.authority().equals(scope)).findFirst().isPresent()) { + return Optional.empty(); + } + + return Optional.of(new RefreshToken(claims)); + } + + @Override + public String getToken() { + return null; + } + + public Jws<Claims> getClaims() { + return claims; + } + + public String getJti() { + return claims.getBody().getId(); + } + + public String getSubject() { + return claims.getBody().getSubject(); + } +} diff --git a/src/main/java/com/moral/service/AccountService.java b/src/main/java/com/moral/service/AccountService.java index 289adcf..68e8371 100644 --- a/src/main/java/com/moral/service/AccountService.java +++ b/src/main/java/com/moral/service/AccountService.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import com.moral.common.bean.PageBean; import com.moral.entity.Account; @@ -24,4 +25,5 @@ Integer getAccountCountByAccountName(String accountName); + Optional<Account> queryAccountByName(String accountName); } diff --git a/src/main/java/com/moral/service/DeviceService.java b/src/main/java/com/moral/service/DeviceService.java index e40048a..707b487 100644 --- a/src/main/java/com/moral/service/DeviceService.java +++ b/src/main/java/com/moral/service/DeviceService.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; import com.moral.common.bean.PageBean; +import com.moral.common.bean.PageResult; import com.moral.entity.Device; public interface DeviceService { @@ -17,6 +18,8 @@ Device getDeviceByMac(String mac); + PageResult query(Integer orgId, String deviceName,Integer pageSize,Integer pageNo); + PageBean queryByPageBean(PageBean pageBean); void deleteByIds(Integer[] ids); diff --git a/src/main/java/com/moral/service/impl/AccountServiceImpl.java b/src/main/java/com/moral/service/impl/AccountServiceImpl.java index be7b39a..d441f1f 100644 --- a/src/main/java/com/moral/service/impl/AccountServiceImpl.java +++ b/src/main/java/com/moral/service/impl/AccountServiceImpl.java @@ -7,15 +7,11 @@ import static org.apache.commons.lang3.StringUtils.isNumeric; import static org.springframework.util.ObjectUtils.isEmpty; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.annotation.Resource; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; @@ -38,7 +34,8 @@ @Service public class AccountServiceImpl implements AccountService { - + @Resource + private BCryptPasswordEncoder encoder; @Resource private AccountMapper accountMapper; @@ -47,7 +44,7 @@ @Resource private OrganizationMapper organizationMapper; - + @Override public Map<String, Object> screenLogin(Map<String, Object> parameters) { Map<String, Object> result = new HashMap<String, Object>(); @@ -147,7 +144,7 @@ if (ObjectUtils.isEmpty(account.getId())) { account.setIsDelete(Constants.IS_DELETE_FALSE); account.setCreateTime(new Date()); - account.setPassword(Crypto.md5(ResourceUtil.getValue("password"))); + account.setPassword(encoder.encode(ResourceUtil.getValue("password"))); return accountMapper.insertSelective(account); } else { return accountMapper.updateByPrimaryKeySelective(account); @@ -171,4 +168,10 @@ return accountMapper.selectCount(account); } + @Override + public Optional<Account> queryAccountByName(String accountName) { + Account account = new Account(); + account.setAccountName(accountName); + return Optional.ofNullable(accountMapper.selectOne(account)); + } } diff --git a/src/main/java/com/moral/service/impl/DeviceServiceImpl.java b/src/main/java/com/moral/service/impl/DeviceServiceImpl.java index 78cbbd7..eefb269 100644 --- a/src/main/java/com/moral/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/moral/service/impl/DeviceServiceImpl.java @@ -4,8 +4,10 @@ import javax.annotation.Resource; +import com.github.pagehelper.Page; import com.moral.common.bean.Constants; import com.moral.common.bean.PageBean; +import com.moral.common.bean.PageResult; import com.moral.common.util.ExampleUtil; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -102,6 +104,18 @@ } @Override + public PageResult query(Integer orgId, String deviceName, Integer pageSize, Integer pageNo) { + if(!ObjectUtils.isEmpty(pageSize)&&!ObjectUtils.isEmpty(pageNo)){ + PageHelper.startPage(pageNo,pageSize); + } + List list = deviceMapper.selectByOrgIdAndDevName(orgId,deviceName); + if(list instanceof Page){ + return new PageResult(((Page) list).getTotal(),list); + } + return new PageResult(null,list); + } + + @Override public PageBean queryByPageBean(PageBean pageBean) { Example example = ExampleUtil.generateExample(ENTITY_CLASS,pageBean); List<Example.Criteria> criteriaList = example.getOredCriteria(); diff --git a/src/main/java/com/moral/service/impl/MonitorPointServiceImpl.java b/src/main/java/com/moral/service/impl/MonitorPointServiceImpl.java index fc0db0d..79cf204 100644 --- a/src/main/java/com/moral/service/impl/MonitorPointServiceImpl.java +++ b/src/main/java/com/moral/service/impl/MonitorPointServiceImpl.java @@ -9,11 +9,9 @@ import com.github.pagehelper.PageHelper; import com.moral.common.bean.Constants; import com.moral.common.bean.PageBean; -import com.moral.common.util.ExampleUtil; -import com.moral.common.util.MyBatisBaseMapUtil; +import com.moral.common.util.*; import org.springframework.stereotype.Service; -import com.moral.common.util.ValidateUtil; import com.moral.entity.MonitorPoint; import com.moral.mapper.MonitorPointMapper; import com.moral.service.MonitorPointService; @@ -24,6 +22,8 @@ public class MonitorPointServiceImpl implements MonitorPointService { @Resource private MonitorPointMapper monitorPointMapper; + @Resource + RedisUtils redisUtils; private static Class ENTITY_CLASS = MonitorPoint.class; @Override public List<MonitorPoint> getMonitorPointsByAreaName(Map<String, Object> parameters) { @@ -32,7 +32,25 @@ } @Override public List<MonitorPoint> queryWithStateByMap(Map<String, Object> params){ - return monitorPointMapper.selectWithStateByMap(params); + List<MonitorPoint> monitorPointList = monitorPointMapper.selectByMap(params); + for(MonitorPoint monitorPoint:monitorPointList){ + loadStateFromRedis(monitorPoint); + } + return monitorPointList; + } + private void loadStateFromRedis(MonitorPoint monitorPoint){ + StringBuilder key = new StringBuilder(); + key.append("*_").append(monitorPoint.getId()).append("_*"); + List<Map> stateList = redisUtils.getList(key.toString(),Map.class); + int state = -1; + if(stateList!=null){ + for (Map deviceState:stateList){ + int s = Integer.parseInt(deviceState.get("state").toString()); + state = s>state&&s<4?s:state; + } + } + state = state==-1?4:state; + monitorPoint.setState(state); } @Override public PageBean queryByPageBean(PageBean pageBean) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b76fb92..f15aa45 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,8 +3,15 @@ session-timeout: 30 tomcat.max-threads: 0 tomcat.uri-encoding: UTF-8 - +spring.profiles: default +moral.security.jwt: + tokenExpirationTime: 15 # Number of minutes + refreshTokenExpTime: 60 # Minutes + tokenIssuer: http://monitor.7drlb.com + tokenSigningKey: xm9EV6Hy5RAFL8EEACIDAwQus spring: + thymeleaf: + cache: false datasource: url: jdbc:mysql://47.96.19.115:3306/monitor_db?characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC username: root @@ -64,4 +71,5 @@ mybatis: mapper-locations: classpath*:/mapper/*Mapper.xml - config-location: classpath:/mapper/mybatis-config.xml \ No newline at end of file + config-location: classpath:/mapper/mybatis-config.xml + diff --git a/src/main/resources/mapper/DeviceMapper.xml b/src/main/resources/mapper/DeviceMapper.xml index fbe298a..cb2cba0 100644 --- a/src/main/resources/mapper/DeviceMapper.xml +++ b/src/main/resources/mapper/DeviceMapper.xml @@ -81,7 +81,7 @@ </if> ) </select> - <select id="getDeviceStatesByAccount" resultType="map"> + <select id="getDeviceStatesByAccount" resultType="java.util.Map"> SELECT COUNT( d.state ) count, d.state @@ -98,8 +98,16 @@ </if> GROUP BY d.state </select> - - <select id="getSensorsByDevice" resultType="map"> + <select id="selectByOrgIdAndDevName" resultMap="BaseResultMap"> + SELECT * from device dev + left join monitor_point mpt on dev.monitor_point_id = mpt.id + where + mpt.organization_id = #{orgId} + <if test="devName!=null and ''!=devName"> + and dev.name like #{devName} + </if> + </select> + <select id="getSensorsByDevice" resultType="java.util.Map"> SELECT s.`key`, s.`name` diff --git a/src/main/resources/mapper/MonitorPointMapper.xml b/src/main/resources/mapper/MonitorPointMapper.xml index 652012a..ee6e6af 100644 --- a/src/main/resources/mapper/MonitorPointMapper.xml +++ b/src/main/resources/mapper/MonitorPointMapper.xml @@ -13,6 +13,7 @@ <result column="address" jdbcType="VARCHAR" property="address" /> <result column="is_delete" jdbcType="CHAR" property="isDelete" /> <result column="description" jdbcType="VARCHAR" property="description" /> + <result column="state" jdbcType="INTEGER" property="state" /> <!-- ������������������������������ --> <association property="areaNames" javaType="com.moral.entity.AreaNames"> <result column="province_name" property="provinceName" jdbcType="VARCHAR" /> @@ -100,10 +101,8 @@ AND mp.city_code = c.city_code </if> </select> - <select id="selectWithStateByMap" parameterType="java.util.Map" resultMap="BaseResultMap"> - - SELECT mpt.*,MAX(dev.state) as state from monitor_point mpt - LEFT JOIN device dev on dev.monitor_point_id = mpt.id + <select id="selectByMap" parameterType="java.util.Map" resultMap="BaseResultMap"> + SELECT mpt.* from monitor_point mpt <where> <if test="@com.moral.common.bean.Constants@isNotSpecialOrgId(orgId)"> mpt.organization_id = #{orgId,jdbcType=VARCHAR} @@ -113,25 +112,7 @@ AND mpt.longitude > #{mapBounds.Le,jdbcType=NUMERIC} AND mpt.latitude < #{mapBounds.Fe,jdbcType=NUMERIC} AND mpt.latitude > #{mapBounds.Ke,jdbcType=NUMERIC} - and state<4 - GROUP BY mpt.`id` ]]> - </where> - UNION - SELECT mpt.*,MAX(dev.state) as state from monitor_point mpt - LEFT JOIN device dev on dev.monitor_point_id = mpt.id - <where> - <if test="@com.moral.common.bean.Constants@isNotSpecialOrgId(orgId)"> - mpt.organization_id = #{orgId,jdbcType=VARCHAR} - </if> - <![CDATA[ - AND mpt.longitude < #{mapBounds.Ge,jdbcType=NUMERIC} - AND mpt.longitude > #{mapBounds.Le,jdbcType=NUMERIC} - AND mpt.latitude < #{mapBounds.Fe,jdbcType=NUMERIC} - AND mpt.latitude > #{mapBounds.Ke,jdbcType=NUMERIC} - and state>3 - GROUP BY mpt.`id` - ]]> </where> </select> </mapper> \ No newline at end of file diff --git a/src/main/resources/system/sysConfig.properties b/src/main/resources/system/sysConfig.properties index cbc54af..d2a12c9 100644 --- a/src/main/resources/system/sysConfig.properties +++ b/src/main/resources/system/sysConfig.properties @@ -26,4 +26,5 @@ e18-standard=9 e19-standard=50 orgId=-1 -password=123456 \ No newline at end of file +password=123456 +noFilters=login \ No newline at end of file diff --git a/src/main/webapp/js/moralmap.js b/src/main/webapp/js/moralmap.js index e26429f..c28f4b8 100644 --- a/src/main/webapp/js/moralmap.js +++ b/src/main/webapp/js/moralmap.js @@ -360,29 +360,19 @@ var pageSize = option["pageSize"] || 20; var pageNo = option["pageNo"] || 1; var url = option['url']; - url += "&page=" + pageNo; - url += "&rows=" + pageSize; + url += "&pageNo=" + pageNo; + url += "&pageSize=" + pageSize; + console.log(url); $.ajax({ type: "get", cache: false, url: url, async: true, - success: function(data) { - if(data) { - if(typeof data !== 'object' && typeof data === 'string') { - try { - data = $.parseJSON(data); - } catch(e) { - return; - } - } - var rows = null; - if(data['rows'] != undefined) { - rows = data['rows']; - } else { - rows = data; - } - if(rows.length == 0) { + success: function(res) { + if(res!=null&&res.total!=null) { + debugger; + var rows = res.data; + if(rows==null||rows.length == 0) { $(option['id']).html("������������������������������"); return; } @@ -417,7 +407,7 @@ outHtml += li; } outHtml += "</ul>"; - var total = data['total']; + var total = res['total']; var totalPage = Math.ceil(total / pageSize); if(totalPage > 1) { outHtml += "<div id='page' class='page_div'></div>"; @@ -494,7 +484,7 @@ //��������������� ������Mark������ moralMap.Monitorpoint = function(option) { var icon = {}; - icon["stateIcons"] = ["img/ico00.png", "img/ico01.png", "img/ico02.png", "img/ico03.png", "img/ico04.png"]; + icon["stateIcons"] = ["/img/ico00.png", "/img/ico01.png", "/img/ico02.png", "/img/ico03.png", "/img/ico04.png"]; icon["width"] = 50; icon["height"] = 50; option["icon"] = icon; diff --git a/src/main/webapp/view/map.jsp b/src/main/webapp/view/map.jsp index 77896b8..6473019 100644 --- a/src/main/webapp/view/map.jsp +++ b/src/main/webapp/view/map.jsp @@ -264,7 +264,7 @@ function showEqus(obj) { var params = moralMap['params']; var mpoint = obj.currentTarget.getOption(); - var url = 'equipment/findAllEqu?mpId=' + mpoint['id'] + "&orgId=" + params['orgId']; + var url = 'get-devices?mpId=' + mpoint['id'] + "&orgId=" + params['orgId']; listView.load(url); moralMap.showPopupbox("#popup_box"); } @@ -371,7 +371,7 @@ // if(endZoom>=moralMap.getZooMConfine()){//������������������������ // loadOverlays("getequipments",addOverEquipments); // }else{ -// loadOverlays("getmonitorpoints",addOverMpoints); +// loadOverlays("get-monitorpoints",addOverMpoints); // } // moralMap.closePopupbox("#popup_box"); }); @@ -383,7 +383,7 @@ if(endZoom>=moralMap.getZooMConfine()){//������������������������ loadOverlays("getequipments",addOverEquipments); }else{ - loadOverlays("getmonitorpoints",addOverMpoints); + loadOverlays("get-monitorpoints",addOverMpoints); } moralMap.closePopupbox("#popup_box"); }); @@ -392,7 +392,7 @@ if(endZoom>=moralMap.getZooMConfine()){//������������������������ loadOverlays("getequipments",addOverEquipments); }else{ - loadOverlays("getmonitorpoints",addOverMpoints); + loadOverlays("get-monitorpoints",addOverMpoints); } moralMap.closePopupbox("#popup_box"); }); @@ -401,7 +401,7 @@ function(e) { var param = encodeURI($("#searchParam").val()); moralMap.showPopupbox("#popup_box"); - var url = 'equipment/findAllEqu?name=' + param + "&orgId=" + params['orgId']; + var url = 'get-devices-for-popup?name=' + param + "&orgId=" + params['orgId']; listView.load(url); } ) diff --git a/src/test/java/com/moral/JavaBeanToJsonOutPrint.java b/src/test/java/com/moral/JavaBeanToJsonOutPrint.java index fbc1ff3..8b9de9d 100644 --- a/src/test/java/com/moral/JavaBeanToJsonOutPrint.java +++ b/src/test/java/com/moral/JavaBeanToJsonOutPrint.java @@ -6,8 +6,11 @@ import com.moral.entity.Device; import com.moral.entity.DeviceAdjustValue; import com.moral.entity.alarm.AlarmConfig; +import com.moral.security.auth.login.LoginMode; +import com.moral.security.model.UserContext; import org.junit.Test; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -15,7 +18,7 @@ public class JavaBeanToJsonOutPrint { @Test public void jsonOutPrintTest(){ - DeviceAdjustValue adjustValue = new DeviceAdjustValue(); + UserContext userContext = UserContext.create("312", LoginMode.Andriod,1,new ArrayList<>()); // adjustValue.setCreateTime(new Date()); // adjustValue.setUpdateTime(new Date()); // adjustValue.setId(0); @@ -23,12 +26,10 @@ // value.put("e1", (float) 1.2); // adjustValue.setValue(value); String json = "{\"createTime\":1516342989358,\"deviceId\":null,\"id\":0,\"updateTime\":1516342989358,\"value\":{\"e1\":1.2}}"; - Map map = JSON.parseObject("{\"e1\":0.0}"); -// String json = JSON.toJSONString(new Device(), -// SerializerFeature.WriteMapNullValue -// ); +// Map map = JSON.parseObject("{\"e1\":0.0}"); + String json1 = JSON.toJSONString(userContext); System.out.printf("\n\n\n\n"); - System.out.printf(json); + System.out.printf(json1); System.out.printf("\n\n\n\n"); } } -- Gitblit v1.8.0